Merge branch 'master' into feature/notify-state-change

pull/241/head
Hunter Long 2019-10-15 08:56:46 -07:00 committed by GitHub
commit c1cc5b5179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 321 additions and 326 deletions

View File

@ -2,7 +2,7 @@ os:
- linux - linux
language: go language: go
go: 1.12.1 go: 1.13
go_import_path: github.com/hunterlong/statping go_import_path: github.com/hunterlong/statping
cache: cache:

View File

@ -232,7 +232,7 @@ dev-deps:
$(GOGET) github.com/ararog/timeago $(GOGET) github.com/ararog/timeago
$(GOGET) gopkg.in/natefinch/lumberjack.v2 $(GOGET) gopkg.in/natefinch/lumberjack.v2
$(GOGET) golang.org/x/crypto/bcrypt $(GOGET) golang.org/x/crypto/bcrypt
$(GOGET) github.com/99designs/gqlgen $(GOGET) github.com/99designs/gqlgen/...
# remove files for a clean compile/build # remove files for a clean compile/build
clean: clean:

View File

@ -241,7 +241,7 @@ func HelpEcho() {
func checkGithubUpdates() (githubResponse, error) { func checkGithubUpdates() (githubResponse, error) {
var gitResp githubResponse var gitResp githubResponse
url := "https://api.github.com/repos/hunterlong/statping/releases/latest" url := "https://api.github.com/repos/hunterlong/statping/releases/latest"
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(10*time.Second)) contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(10*time.Second), true)
if err != nil { if err != nil {
return githubResponse{}, err return githubResponse{}, err
} }

View File

@ -211,9 +211,9 @@ func (s *Service) checkHttp(record bool) *Service {
} }
if s.Method == "POST" { if s.Method == "POST" {
content, res, err = utils.HttpRequest(s.Domain, s.Method, "application/json", headers, bytes.NewBuffer([]byte(s.PostData.String)), timeout) content, res, err = utils.HttpRequest(s.Domain, s.Method, "application/json", headers, bytes.NewBuffer([]byte(s.PostData.String)), timeout, s.VerifySSL.Bool)
} else { } else {
content, res, err = utils.HttpRequest(s.Domain, s.Method, nil, headers, nil, timeout) content, res, err = utils.HttpRequest(s.Domain, s.Method, nil, headers, nil, timeout, s.VerifySSL.Bool)
} }
if err != nil { if err != nil {
if record { if record {
@ -261,8 +261,9 @@ func recordSuccess(s *Service) {
} }
utils.Log(1, fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000)) utils.Log(1, fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
s.CreateHit(hit) s.CreateHit(hit)
s.Online = true
notifier.OnSuccess(s.Service) notifier.OnSuccess(s.Service)
s.Online = true
s.SuccessNotified = true
} }
// recordFailure will create a new 'Failure' record in the database for a offline service // recordFailure will create a new 'Failure' record in the database for a offline service
@ -277,5 +278,8 @@ func recordFailure(s *Service, issue string) {
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000)) utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
s.CreateFailure(fail) s.CreateFailure(fail)
s.Online = false s.Online = false
s.SuccessNotified = false
s.UpdateNotify = CoreApp.UpdateNotify.Bool
s.DownText = s.DowntimeText()
notifier.OnFailure(s.Service, fail.Failure) notifier.OnFailure(s.Service, fail.Failure)
} }

View File

@ -15,7 +15,11 @@
package notifier package notifier
import "github.com/hunterlong/statping/types" import (
"fmt"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
)
// OnSave will trigger a notifier when it has been saved - Notifier interface // OnSave will trigger a notifier when it has been saved - Notifier interface
func OnSave(method string) { func OnSave(method string) {
@ -34,12 +38,26 @@ func OnFailure(s *types.Service, f *types.Failure) {
if !s.AllowNotifications.Bool { if !s.AllowNotifications.Bool {
return return
} }
for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { // check if User wants to receive every Status Change
comm.(BasicEvents).OnFailure(s, f) if s.UpdateNotify {
// send only if User hasn't been already notified about the Downtime
if !s.UserNotified {
s.UserNotified = true
goto sendMessages
} else {
return
} }
} }
sendMessages:
for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) {
notifier := comm.(Notifier).Select()
utils.Log(1, fmt.Sprintf("Sending failure %v notification for service %v", notifier.Method, s.Name))
comm.(BasicEvents).OnFailure(s, f)
}
}
} }
// OnSuccess will be triggered when a service is successful - BasicEvents interface // OnSuccess will be triggered when a service is successful - BasicEvents interface
@ -47,18 +65,26 @@ func OnSuccess(s *types.Service) {
if !s.AllowNotifications.Bool { if !s.AllowNotifications.Bool {
return return
} }
// check if User wants to receive every Status Change
if s.UpdateNotify && s.UserNotified {
s.UserNotified = false
}
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) { if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) {
notifier := comm.(Notifier).Select()
utils.Log(1, fmt.Sprintf("Sending successful %v notification for service %v", notifier.Method, s.Name))
comm.(BasicEvents).OnSuccess(s) comm.(BasicEvents).OnSuccess(s)
} }
} }
} }
// OnNewService is triggered when a new service is created - ServiceEvents interface // OnNewService is triggered when a new service is created - ServiceEvents interface
func OnNewService(s *types.Service) { func OnNewService(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending new service notification for service %v", s.Name))
comm.(ServiceEvents).OnNewService(s) comm.(ServiceEvents).OnNewService(s)
} }
} }
@ -71,6 +97,7 @@ func OnUpdatedService(s *types.Service) {
} }
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated service notification for service %v", s.Name))
comm.(ServiceEvents).OnUpdatedService(s) comm.(ServiceEvents).OnUpdatedService(s)
} }
} }
@ -83,6 +110,7 @@ func OnDeletedService(s *types.Service) {
} }
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
comm.(ServiceEvents).OnDeletedService(s) comm.(ServiceEvents).OnDeletedService(s)
} }
} }
@ -92,6 +120,7 @@ func OnDeletedService(s *types.Service) {
func OnNewUser(u *types.User) { func OnNewUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending new user notification for user %v", u.Username))
comm.(UserEvents).OnNewUser(u) comm.(UserEvents).OnNewUser(u)
} }
} }
@ -101,6 +130,7 @@ func OnNewUser(u *types.User) {
func OnUpdatedUser(u *types.User) { func OnUpdatedUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated user notification for user %v", u.Username))
comm.(UserEvents).OnUpdatedUser(u) comm.(UserEvents).OnUpdatedUser(u)
} }
} }
@ -110,6 +140,7 @@ func OnUpdatedUser(u *types.User) {
func OnDeletedUser(u *types.User) { func OnDeletedUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
comm.(UserEvents).OnDeletedUser(u) comm.(UserEvents).OnDeletedUser(u)
} }
} }
@ -119,6 +150,7 @@ func OnDeletedUser(u *types.User) {
func OnUpdatedCore(c *types.Core) { func OnUpdatedCore(c *types.Core) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated core notification"))
comm.(CoreEvents).OnUpdatedCore(c) comm.(CoreEvents).OnUpdatedCore(c)
} }
} }
@ -146,6 +178,7 @@ func OnNewNotifier(n *Notification) {
func OnUpdatedNotifier(n *Notification) { func OnUpdatedNotifier(n *Notification) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated notifier for %v", n.Id))
comm.(NotifierEvents).OnUpdatedNotifier(n) comm.(NotifierEvents).OnUpdatedNotifier(n)
} }
} }

View File

@ -63,6 +63,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
"USE_CDN": func() bool { "USE_CDN": func() bool {
return core.CoreApp.UseCdn.Bool return core.CoreApp.UseCdn.Bool
}, },
"UPDATENOTIFY": func() bool {
return core.CoreApp.UpdateNotify.Bool
},
"QrAuth": func() string { "QrAuth": func() string {
return fmt.Sprintf("statping://setup?domain=%v&api=%v", core.CoreApp.Domain, core.CoreApp.ApiSecret) return fmt.Sprintf("statping://setup?domain=%v&api=%v", core.CoreApp.Domain, core.CoreApp.ApiSecret)
}, },

View File

@ -62,11 +62,15 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
timeFloat, _ := strconv.ParseFloat(timezone, 10) timeFloat, _ := strconv.ParseFloat(timezone, 10)
app.Timezone = float32(timeFloat) app.Timezone = float32(timeFloat)
app.UpdateNotify = types.NewNullBool(form.Get("update_notify") == "true")
app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on") app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on")
core.CoreApp, err = core.UpdateCore(app) core.CoreApp, err = core.UpdateCore(app)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error())) utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error()))
} }
//notifiers.OnSettingsSaved(core.CoreApp.ToCore()) //notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings") ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
} }
@ -144,7 +148,7 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
// commaToService will convert a CSV comma delimited string slice to a Service type // commaToService will convert a CSV comma delimited string slice to a Service type
// this function is used for the bulk import services feature // this function is used for the bulk import services feature
func commaToService(s []string) (*types.Service, error) { func commaToService(s []string) (*types.Service, error) {
if len(s) != 16 { if len(s) != 17 {
err := fmt.Errorf("does not have the expected amount of %v columns for a service", 16) err := fmt.Errorf("does not have the expected amount of %v columns for a service", 16)
return nil, err return nil, err
} }
@ -169,6 +173,11 @@ func commaToService(s []string) (*types.Service, error) {
return nil, err return nil, err
} }
verifySsl, err := strconv.ParseBool(s[16])
if err != nil {
return nil, err
}
newService := &types.Service{ newService := &types.Service{
Name: s[0], Name: s[0],
Domain: s[1], Domain: s[1],
@ -185,6 +194,7 @@ func commaToService(s []string) (*types.Service, error) {
GroupId: int(utils.ToInt(s[13])), GroupId: int(utils.ToInt(s[13])),
Headers: types.NewNullString(s[14]), Headers: types.NewNullString(s[14]),
Permalink: types.NewNullString(s[15]), Permalink: types.NewNullString(s[15]),
VerifySSL: types.NewNullBool(verifySsl),
} }
return newService, nil return newService, nil

View File

@ -59,7 +59,7 @@ func init() {
// Send will send a HTTP Post to the discord API. It accepts type: []byte // Send will send a HTTP Post to the discord API. It accepts type: []byte
func (u *discord) Send(msg interface{}) error { func (u *discord) Send(msg interface{}) error {
message := msg.(string) message := msg.(string)
_, _, err := utils.HttpRequest(discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second)) _, _, err := utils.HttpRequest(discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true)
return err return err
} }
@ -75,9 +75,14 @@ func (u *discord) OnFailure(s *types.Service, f *types.Failure) {
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (u *discord) OnSuccess(s *types.Service) { func (u *discord) OnSuccess(s *types.Service) {
if !s.Online { if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name) var msg interface{}
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
} }
} }
@ -93,7 +98,7 @@ func (u *discord) OnSave() error {
func (u *discord) OnTest() error { func (u *discord) OnTest() error {
outError := errors.New("Incorrect discord URL, please confirm URL is correct") outError := errors.New("Incorrect discord URL, please confirm URL is correct")
message := `{"content": "Testing the discord notifier"}` message := `{"content": "Testing the discord notifier"}`
contents, _, err := utils.HttpRequest(discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second)) contents, _, err := utils.HttpRequest(discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true)
if string(contents) == "" { if string(contents) == "" {
return nil return nil
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"html/template" "html/template"
"net/smtp"
"time" "time"
) )
@ -199,11 +198,17 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) {
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (u *email) OnSuccess(s *types.Service) { func (u *email) OnSuccess(s *types.Service) {
if !s.Online { if !s.Online || !s.SuccessNotified {
var msg string
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
email := &emailOutgoing{ email := &emailOutgoing{
To: u.Var2, To: u.Var2,
Subject: fmt.Sprintf("Service %v is Back Online", s.Name), Subject: msg,
Template: mainEmailTemplate, Template: mainEmailTemplate,
Data: interface{}(s), Data: interface{}(s),
From: u.Var1, From: u.Var1,
@ -225,24 +230,6 @@ func (u *email) OnSave() error {
// OnTest triggers when this notifier has been saved // OnTest triggers when this notifier has been saved
func (u *email) OnTest() error { func (u *email) OnTest() error {
host := fmt.Sprintf("%v:%v", u.Host, u.Port)
dial, err := smtp.Dial(host)
if err != nil {
return err
}
if u.ApiKey != "true" {
err = dial.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
return err
}
}
if u.Username != "" || u.Password != "" {
auth := smtp.PlainAuth("", u.Username, u.Password, host)
err = dial.Auth(auth)
if err != nil {
return err
}
}
testService := &types.Service{ testService := &types.Service{
Id: 1, Id: 1,
Name: "Example Service", Name: "Example Service",
@ -261,11 +248,10 @@ func (u *email) OnTest() error {
To: u.Var2, To: u.Var2,
Subject: fmt.Sprintf("Service %v is Back Online", testService.Name), Subject: fmt.Sprintf("Service %v is Back Online", testService.Name),
Template: mainEmailTemplate, Template: mainEmailTemplate,
Data: interface{}(testService), Data: testService,
From: u.Var1, From: u.Var1,
} }
err = u.Send(email) return u.dialSend(email)
return err
} }
func (u *email) dialSend(email *emailOutgoing) error { func (u *email) dialSend(email *emailOutgoing) error {

View File

@ -62,7 +62,7 @@ func (u *lineNotifier) Send(msg interface{}) error {
v := url.Values{} v := url.Values{}
v.Set("message", message) v.Set("message", message)
headers := []string{fmt.Sprintf("Authorization=Bearer %v", u.ApiSecret)} headers := []string{fmt.Sprintf("Authorization=Bearer %v", u.ApiSecret)}
_, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second)) _, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true)
return err return err
} }
@ -78,16 +78,22 @@ func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) {
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (u *lineNotifier) OnSuccess(s *types.Service) { func (u *lineNotifier) OnSuccess(s *types.Service) {
if !s.Online { if !s.Online || !s.SuccessNotified {
var msg string
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
} }
} }
// OnSave triggers when this notifier has been saved // OnSave triggers when this notifier has been saved
func (u *lineNotifier) OnSave() error { func (u *lineNotifier) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method)
// Do updating stuff here utils.Log(1, msg)
u.AddQueue("saved", message)
return nil return nil
} }

View File

@ -105,10 +105,16 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) {
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (u *mobilePush) OnSuccess(s *types.Service) { func (u *mobilePush) OnSuccess(s *types.Service) {
data := dataJson(s, nil) data := dataJson(s, nil)
if !s.Online { if !s.Online || !s.SuccessNotified {
var msgStr string
if s.UpdateNotify {
s.UpdateNotify = false
}
msgStr = s.DownText
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
msg := &pushArray{ msg := &pushArray{
Message: fmt.Sprintf("Your service '%v' is back online!", s.Name), Message: msgStr,
Title: "Service Online", Title: "Service Online",
Topic: mobileIdentifier, Topic: mobileIdentifier,
Data: data, Data: data,
@ -176,7 +182,7 @@ func pushRequest(msg *pushArray) ([]byte, error) {
return nil, err return nil, err
} }
url := "https://push.statping.com/api/push" url := "https://push.statping.com/api/push"
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second)) body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second), true)
return body, err return body, err
} }

View File

@ -29,7 +29,7 @@ import (
const ( const (
slackMethod = "slack" slackMethod = "slack"
failingTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> has just received a Failure notification based on your expected results. {{.Service.Name}} responded with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected Status Code", "value": "{{.Service.ExpectedStatus}}", "short": true }, { "title": "Received Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#FF0000", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }` failingTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> has just received a Failure notification based on your expected results. {{.Service.Name}} responded with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected Status Code", "value": "{{.Service.ExpectedStatus}}", "short": true }, { "title": "Received Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ,{ "title": "Error Message", "value": "{{.Issue}}", "short": false } ], "color": "#FF0000", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
successTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> is now back online and meets your expected responses.", "color": "#00FF00", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }` successTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> is now back online and meets your expected responses.", "color": "#00FF00", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
slackText = `{"text":"{{.}}"}` slackText = `{"text":"{{.}}"}`
) )
@ -79,12 +79,13 @@ type slackMessage struct {
Service *types.Service Service *types.Service
Template string Template string
Time int64 Time int64
Issue string
} }
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string // Send will send a HTTP Post to the slack webhooker API. It accepts type: string
func (u *slack) Send(msg interface{}) error { func (u *slack) Send(msg interface{}) error {
message := msg.(string) message := msg.(string)
_, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second)) _, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true)
return err return err
} }
@ -93,7 +94,7 @@ func (u *slack) Select() *notifier.Notification {
} }
func (u *slack) OnTest() error { func (u *slack) OnTest() error {
contents, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second)) contents, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second), true)
if string(contents) != "ok" { if string(contents) != "ok" {
return errors.New("The slack response was incorrect, check the URL") return errors.New("The slack response was incorrect, check the URL")
} }
@ -106,6 +107,7 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
Service: s, Service: s,
Template: failingTemplate, Template: failingTemplate,
Time: time.Now().Unix(), Time: time.Now().Unix(),
Issue: f.Issue,
} }
parseSlackMessage(s.Id, failingTemplate, message) parseSlackMessage(s.Id, failingTemplate, message)
} }

View File

@ -78,7 +78,7 @@ func (u *telegram) Send(msg interface{}) error {
v.Set("text", message) v.Set("text", message)
rb := *strings.NewReader(v.Encode()) rb := *strings.NewReader(v.Encode())
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second)) contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true)
success, _ := telegramSuccess(contents) success, _ := telegramSuccess(contents)
if !success { if !success {
@ -97,9 +97,14 @@ func (u *telegram) OnFailure(s *types.Service, f *types.Failure) {
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (u *telegram) OnSuccess(s *types.Service) { func (u *telegram) OnSuccess(s *types.Service) {
if !s.Online { if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) var msg interface{}
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
} }
} }

View File

@ -89,7 +89,7 @@ func (u *twilio) Send(msg interface{}) error {
v.Set("Body", message) v.Set("Body", message)
rb := *strings.NewReader(v.Encode()) rb := *strings.NewReader(v.Encode())
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second)) contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true)
success, _ := twilioSuccess(contents) success, _ := twilioSuccess(contents)
if !success { if !success {
errorOut := twilioError(contents) errorOut := twilioError(contents)
@ -107,9 +107,14 @@ func (u *twilio) OnFailure(s *types.Service, f *types.Failure) {
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (u *twilio) OnSuccess(s *types.Service) { func (u *twilio) OnSuccess(s *types.Service) {
if !s.Online { if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) var msg string
if s.UpdateNotify {
s.UpdateNotify = false
}
msg = s.DownText
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
} }
} }

View File

@ -7,66 +7,54 @@
/* Mobile Service Container */ /* Mobile Service Container */
HTML, BODY { HTML, BODY {
background-color: #fcfcfc; background-color: #fcfcfc;
padding-bottom: 10px; padding-bottom: 10px; }
}
.container { .container {
padding-top: 20px; padding-top: 20px;
padding-bottom: 25px; padding-bottom: 25px;
max-width: 860px; max-width: 860px;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; }
}
.header-title { .header-title {
color: #464646; color: #464646; }
}
.header-desc { .header-desc {
color: #939393; color: #939393; }
}
.btn { .btn {
border-radius: 0.2rem; border-radius: 0.2rem; }
}
.online_list .badge { .online_list .badge {
margin-top: 0.2rem; margin-top: 0.2rem; }
}
.navbar { .navbar {
margin-bottom: 30px; margin-bottom: 30px; }
}
.btn-sm { .btn-sm {
line-height: 1.3; line-height: 1.3;
font-size: 0.75rem; font-size: 0.75rem; }
}
.view_service_btn { .view_service_btn {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
right: 40px; right: 40px; }
}
.service_lower_info { .service_lower_info {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
left: 40px; left: 40px;
color: #d1ffca; color: #d1ffca;
font-size: 0.85rem; font-size: 0.85rem; }
}
.lg_number { .lg_number {
font-size: 2.3rem; font-size: 2.3rem;
font-weight: bold; font-weight: bold;
display: block; display: block;
color: #4f4f4f; color: #4f4f4f; }
}
.stats_area { .stats_area {
text-align: center; text-align: center;
color: #a5a5a5; color: #a5a5a5; }
}
.lower_canvas { .lower_canvas {
height: 3.4rem; height: 3.4rem;
@ -74,101 +62,82 @@ HTML, BODY {
background-color: #48d338; background-color: #48d338;
padding: 15px 10px; padding: 15px 10px;
margin-left: 0px !important; margin-left: 0px !important;
margin-right: 0px !important; margin-right: 0px !important; }
}
.lower_canvas SPAN { .lower_canvas SPAN {
font-size: 1rem; font-size: 1rem;
color: #fff; color: #fff; }
}
.footer { .footer {
text-decoration: none; text-decoration: none;
margin-top: 20px; margin-top: 20px; }
}
.footer A { .footer A {
color: #8d8d8d; color: #8d8d8d;
text-decoration: none; text-decoration: none; }
}
.footer A:HOVER { .footer A:HOVER {
color: #6d6d6d; color: #6d6d6d; }
}
.badge { .badge {
color: white; color: white;
border-radius: 0.2rem; border-radius: 0.2rem; }
}
.btn-group { .btn-group {
height: 25px; height: 25px; }
}
.btn-group A { .btn-group A {
padding: 0.1rem 0.75rem; padding: 0.1rem .75rem;
font-size: 0.8rem; font-size: 0.8rem; }
}
.card-body .badge { .card-body .badge {
color: #fff; color: #fff; }
}
.nav-pills .nav-link { .nav-pills .nav-link {
border-radius: 0.2rem; border-radius: 0.2rem; }
}
.form-control { .form-control {
border-radius: 0.2rem; border-radius: 0.2rem; }
}
.card { .card {
background-color: #ffffff; background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.125); border: 1px solid rgba(0, 0, 0, 0.125); }
}
.card-body { .card-body {
overflow: hidden; overflow: hidden; }
}
.card-body H4 A { .card-body H4 A {
color: #444444; color: #444444;
text-decoration: none; text-decoration: none; }
}
.chart-container { .chart-container {
position: relative; position: relative;
height: 170px; height: 170px;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden; }
}
.service-chart-container { .service-chart-container {
position: relative; position: relative;
height: 400px; height: 400px;
width: 100%; width: 100%; }
}
.service-chart-heatmap { .service-chart-heatmap {
position: relative; position: relative;
height: 300px; height: 300px;
width: 100%; width: 100%; }
}
.inputTags-field { .inputTags-field {
border: 0; border: 0;
background-color: transparent; background-color: transparent;
padding-top: 0.13rem; padding-top: .13rem; }
}
input.inputTags-field:focus { input.inputTags-field:focus {
outline-width: 0; outline-width: 0; }
}
.inputTags-list { .inputTags-list {
display: block; display: block;
width: 100%; width: 100%;
min-height: calc(2.25rem + 2px); min-height: calc(2.25rem + 2px);
padding: 0.2rem 0.35rem; padding: .2rem .35rem;
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -176,9 +145,8 @@ input.inputTags-field:focus {
background-color: #fff; background-color: #fff;
background-clip: padding-box; background-clip: padding-box;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: 0.25rem; border-radius: .25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; }
}
.inputTags-item { .inputTags-item {
background-color: #3aba39; background-color: #3aba39;
@ -186,81 +154,63 @@ input.inputTags-field:focus {
padding: 5px 8px; padding: 5px 8px;
font-size: 10pt; font-size: 10pt;
color: white; color: white;
border-radius: 4px; border-radius: 4px; }
}
.inputTags-item .close-item { .inputTags-item .close-item {
margin-left: 6px; margin-left: 6px;
font-size: 13pt; font-size: 13pt;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer; }
}
.btn-primary { .btn-primary {
background-color: #3e9bff; background-color: #3e9bff;
border-color: #006fe6; border-color: #006fe6;
color: white; color: white; }
}
.btn-primary.dyn-dark { .btn-primary.dyn-dark {
background-color: #32a825 !important; background-color: #32a825 !important;
border-color: #2c9320 !important; border-color: #2c9320 !important; }
}
.btn-primary.dyn-light { .btn-primary.dyn-light {
background-color: #75de69 !important; background-color: #75de69 !important;
border-color: #88e37e !important; border-color: #88e37e !important; }
}
.btn-success { .btn-success {
background-color: #47d337; background-color: #47d337; }
}
.btn-success.dyn-dark { .btn-success.dyn-dark {
background-color: #32a825 !important; background-color: #32a825 !important;
border-color: #2c9320 !important; border-color: #2c9320 !important; }
}
.btn-success.dyn-light { .btn-success.dyn-light {
background-color: #75de69 !important; background-color: #75de69 !important;
border-color: #88e37e !important; border-color: #88e37e !important; }
}
.btn-danger { .btn-danger {
background-color: #dd3545; background-color: #dd3545; }
}
.btn-danger.dyn-dark { .btn-danger.dyn-dark {
background-color: #b61f2d !important; background-color: #b61f2d !important;
border-color: #a01b28 !important; border-color: #a01b28 !important; }
}
.btn-danger.dyn-light { .btn-danger.dyn-light {
background-color: #e66975 !important; background-color: #e66975 !important;
border-color: #e97f89 !important; border-color: #e97f89 !important; }
}
.bg-success { .bg-success {
background-color: #47d337 !important; background-color: #47d337 !important; }
}
.bg-danger { .bg-danger {
background-color: #dd3545 !important; background-color: #dd3545 !important; }
}
.bg-success .dyn-dark { .bg-success .dyn-dark {
background-color: #35b027 !important; background-color: #35b027 !important; }
}
.bg-danger .dyn-dark { .bg-danger .dyn-dark {
background-color: #bf202f !important; background-color: #bf202f !important; }
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link { .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
background-color: #13a00d; background-color: #13a00d; }
}
.nav-pills A { .nav-pills A {
color: #424242; color: #424242; }
}
.nav-pills I { .nav-pills I {
margin-right: 10px; margin-right: 10px; }
}
.CodeMirror { .CodeMirror {
/* Bootstrap Settings */ /* Bootstrap Settings */
@ -280,26 +230,23 @@ input.inputTags-field:focus {
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
/* Code Mirror Settings */ /* Code Mirror Settings */
font-family: monospace; font-family: monospace;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
height: 80vh; height: 80vh; }
}
.CodeMirror-focused { .CodeMirror-focused {
/* Bootstrap Settings */ /* Bootstrap Settings */
border-color: #66afe9; border-color: #66afe9;
outline: 0; outline: 0;
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 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; }
}
.switch { .switch {
font-size: 1rem; font-size: 1rem;
position: relative; position: relative; }
}
.switch input { .switch input {
position: absolute; position: absolute;
@ -310,8 +257,7 @@ input.inputTags-field:focus {
clip: rect(0 0 0 0); clip: rect(0 0 0 0);
clip-path: inset(50%); clip-path: inset(50%);
overflow: hidden; overflow: hidden;
padding: 0; padding: 0; }
}
.switch input + label { .switch input + label {
position: relative; position: relative;
@ -324,26 +270,23 @@ input.inputTags-field:focus {
outline: none; outline: none;
user-select: none; user-select: none;
vertical-align: middle; vertical-align: middle;
text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); }
}
.switch input + label::before, .switch input + label::before,
.switch input + label::after { .switch input + label::after {
content: ""; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: calc(calc(2.375rem * .8) * 2); width: calc(calc(2.375rem * .8) * 2);
bottom: 0; bottom: 0;
display: block; display: block; }
}
.switch input + label::before { .switch input + label::before {
right: 0; right: 0;
background-color: #dee2e6; background-color: #dee2e6;
border-radius: calc(2.375rem * .8); border-radius: calc(2.375rem * .8);
transition: 0.2s all; transition: 0.2s all; }
}
.switch input + label::after { .switch input + label::after {
top: 2px; top: 2px;
@ -352,137 +295,105 @@ input.inputTags-field:focus {
height: calc(calc(2.375rem * .8) - calc(2px * 2)); height: calc(calc(2.375rem * .8) - calc(2px * 2));
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
transition: 0.2s all; transition: 0.2s all; }
}
.switch input:checked + label::before { .switch input:checked + label::before {
background-color: #08d; background-color: #08d; }
}
.switch input:checked + label::after { .switch input:checked + label::after {
margin-left: calc(2.375rem * .8); margin-left: calc(2.375rem * .8); }
}
.switch input:focus + label::before { .switch input:focus + label::before {
outline: none; outline: none;
box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); }
}
.switch input:disabled + label { .switch input:disabled + label {
color: #868e96; color: #868e96;
cursor: not-allowed; cursor: not-allowed; }
}
.switch input:disabled + label::before { .switch input:disabled + label::before {
background-color: #e9ecef; background-color: #e9ecef; }
}
.switch.switch-sm { .switch.switch-sm {
font-size: 0.875rem; font-size: 0.875rem; }
}
.switch.switch-sm input + label { .switch.switch-sm input + label {
min-width: calc(calc(1.9375rem * .8) * 2); min-width: calc(calc(1.9375rem * .8) * 2);
height: calc(1.9375rem * .8); height: calc(1.9375rem * .8);
line-height: calc(1.9375rem * .8); line-height: calc(1.9375rem * .8);
text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); }
}
.switch.switch-sm input + label::before { .switch.switch-sm input + label::before {
width: calc(calc(1.9375rem * .8) * 2); width: calc(calc(1.9375rem * .8) * 2); }
}
.switch.switch-sm input + label::after { .switch.switch-sm input + label::after {
width: calc(calc(1.9375rem * .8) - calc(2px * 2)); width: calc(calc(1.9375rem * .8) - calc(2px * 2));
height: calc(calc(1.9375rem * .8) - calc(2px * 2)); height: calc(calc(1.9375rem * .8) - calc(2px * 2)); }
}
.switch.switch-sm input:checked + label::after { .switch.switch-sm input:checked + label::after {
margin-left: calc(1.9375rem * .8); margin-left: calc(1.9375rem * .8); }
}
.switch.switch-lg { .switch.switch-lg {
font-size: 1.25rem; font-size: 1.25rem; }
}
.switch.switch-lg input + label { .switch.switch-lg input + label {
min-width: calc(calc(3rem * .8) * 2); min-width: calc(calc(3rem * .8) * 2);
height: calc(3rem * .8); height: calc(3rem * .8);
line-height: calc(3rem * .8); line-height: calc(3rem * .8);
text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); }
}
.switch.switch-lg input + label::before { .switch.switch-lg input + label::before {
width: calc(calc(3rem * .8) * 2); width: calc(calc(3rem * .8) * 2); }
}
.switch.switch-lg input + label::after { .switch.switch-lg input + label::after {
width: calc(calc(3rem * .8) - calc(2px * 2)); width: calc(calc(3rem * .8) - calc(2px * 2));
height: calc(calc(3rem * .8) - calc(2px * 2)); height: calc(calc(3rem * .8) - calc(2px * 2)); }
}
.switch.switch-lg input:checked + label::after { .switch.switch-lg input:checked + label::after {
margin-left: calc(3rem * .8); margin-left: calc(3rem * .8); }
}
.switch + .switch { .switch + .switch {
margin-left: 1rem; margin-left: 1rem; }
}
@keyframes pulse_animation { @keyframes pulse_animation {
0% { 0% {
transform: scale(1); transform: scale(1); }
}
30% { 30% {
transform: scale(1); transform: scale(1); }
}
40% { 40% {
transform: scale(1.02); transform: scale(1.02); }
}
50% { 50% {
transform: scale(1); transform: scale(1); }
}
60% { 60% {
transform: scale(1); transform: scale(1); }
}
70% { 70% {
transform: scale(1.05); transform: scale(1.05); }
}
80% { 80% {
transform: scale(1); transform: scale(1); }
}
100% { 100% {
transform: scale(1); transform: scale(1); } }
}
}
.pulse { .pulse {
animation-name: pulse_animation; animation-name: pulse_animation;
animation-duration: 1500ms; animation-duration: 1500ms;
transform-origin: 70% 70%; transform-origin: 70% 70%;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: linear; animation-timing-function: linear; }
}
@keyframes glow-grow { @keyframes glow-grow {
0% { 0% {
opacity: 0; opacity: 0;
transform: scale(1); transform: scale(1); }
}
80% { 80% {
opacity: 1; opacity: 1; }
}
100% { 100% {
transform: scale(2); transform: scale(2);
opacity: 0; opacity: 0; } }
}
}
.pulse-glow { .pulse-glow {
animation-name: glow-grown; animation-name: glow-grown;
animation-duration: 100ms; animation-duration: 100ms;
transform-origin: 70% 30%; transform-origin: 70% 30%;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: linear; animation-timing-function: linear; }
}
.pulse-glow:before, .pulse-glow:before,
.pulse-glow:after { .pulse-glow:after {
@ -494,12 +405,10 @@ input.inputTags-field:focus {
right: 2.15rem; right: 2.15rem;
border-radius: 0; border-radius: 0;
box-shadow: 0 0 6px #47d337; box-shadow: 0 0 6px #47d337;
animation: glow-grow 2s ease-out infinite; animation: glow-grow 2s ease-out infinite; }
}
.sortable_drag { .sortable_drag {
background-color: #0000000f; background-color: #0000000f; }
}
.drag_icon { .drag_icon {
cursor: move; cursor: move;
@ -513,139 +422,112 @@ input.inputTags-field:focus {
margin-right: 5px; margin-right: 5px;
margin-left: -10px; margin-left: -10px;
text-align: center; text-align: center;
color: #b1b1b1; color: #b1b1b1; }
}
/* (Optional) Apply a "closed-hand" cursor during drag operation. */ /* (Optional) Apply a "closed-hand" cursor during drag operation. */
.drag_icon:active { .drag_icon:active {
cursor: grabbing; cursor: grabbing;
cursor: -moz-grabbing; cursor: -moz-grabbing;
cursor: -webkit-grabbing; cursor: -webkit-grabbing; }
}
.switch_btn { .switch_btn {
float: right; float: right;
margin: -1px 0px 0px 0px; margin: -1px 0px 0px 0px;
display: block; display: block; }
}
#start_container { #start_container {
position: absolute; position: absolute;
z-index: 99999; z-index: 99999;
margin-top: 20px; margin-top: 20px; }
}
#end_container { #end_container {
position: absolute; position: absolute;
z-index: 99999; z-index: 99999;
margin-top: 20px; margin-top: 20px;
right: 0; right: 0; }
}
.pointer { .pointer {
cursor: pointer; cursor: pointer; }
}
.jumbotron { .jumbotron {
background-color: white; background-color: white; }
}
.toggle-service { .toggle-service {
font-size: 18pt; font-size: 18pt;
float: left; float: left;
margin: 2px 3px 0 0; margin: 2px 3px 0 0;
cursor: pointer; cursor: pointer; }
}
@media (max-width: 767px) { @media (max-width: 767px) {
HTML, BODY { HTML, BODY {
background-color: #fcfcfc; background-color: #fcfcfc; }
}
.sm-container { .sm-container {
margin-top: 0px !important; margin-top: 0px !important;
padding: 0 !important; padding: 0 !important; }
}
.list-group-item H5 { .list-group-item H5 {
font-size: 0.9rem; font-size: 0.9rem; }
}
.container { .container {
padding: 0px !important; padding: 0px !important;
padding-top: 15px !important; padding-top: 15px !important; }
}
.group_header { .group_header {
margin-left: 15px; margin-left: 15px; }
}
.navbar { .navbar {
margin-left: 0px; margin-left: 0px;
margin-top: 0px; margin-top: 0px;
width: 100%; width: 100%;
margin-bottom: 0; margin-bottom: 0; }
}
.btn-sm { .btn-sm {
line-height: 0.9rem; line-height: 0.9rem;
font-size: 0.65rem; font-size: 0.65rem; }
}
.full-col-12 { .full-col-12 {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px; }
}
.card { .card {
border: 0; border: 0;
border-radius: 0rem; border-radius: 0rem;
padding: 0; padding: 0;
background-color: #ffffff; background-color: #ffffff; }
}
.card-body { .card-body {
font-size: 10pt; font-size: 10pt;
padding: 0px 10px; padding: 0px 10px; }
}
.lg_number { .lg_number {
font-size: 7.8vw; font-size: 7.8vw; }
}
.stats_area { .stats_area {
margin-top: 1.5rem !important; margin-top: 1.5rem !important;
margin-bottom: 1.5rem !important; margin-bottom: 1.5rem !important; }
}
.stats_area .col-4 { .stats_area .col-4 {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
font-size: 0.6rem; font-size: 0.6rem; }
}
.list-group-item { .list-group-item {
border-top: 1px solid #e4e4e4; border-top: 1px solid #e4e4e4;
border: 0px; border: 0px; }
}
.list-group-item:first-child { .list-group-item:first-child {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0; }
}
.list-group-item:last-child { .list-group-item:last-child {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0; }
}
.list-group-item P { .list-group-item P {
font-size: 0.7rem; font-size: 0.7rem; }
}
.service-chart-container { .service-chart-container {
height: 200px; height: 200px; } }
}
}
/*# sourceMappingURL=base.css.map */ /*# sourceMappingURL=base.css.map */

View File

@ -90,7 +90,7 @@ $('.toggle-service').on('click',function(e) {
let obj = $(this); let obj = $(this);
let serviceId = obj.attr("data-id"); let serviceId = obj.attr("data-id");
let online = obj.attr("data-online"); let online = obj.attr("data-online");
let d = confirm("Do you want to "+(online ? "stop" : "start")+" checking this service?"); let d = confirm("Do you want to "+(eval(online) ? "stop" : "start")+" checking this service?");
if (d) { if (d) {
$.ajax({ $.ajax({
url: "/api/services/" + serviceId + "/running", url: "/api/services/" + serviceId + "/running",
@ -145,6 +145,9 @@ $('select#service_type').on('change', function() {
async function RenderChart(chart, service, start=0, end=9999999999, group="hour", retry=true) { async function RenderChart(chart, service, start=0, end=9999999999, group="hour", retry=true) {
if (!chart.el) {
return
}
let chartData = await ChartLatency(service, start, end, group, retry); let chartData = await ChartLatency(service, start, end, group, retry);
if (!chartData) { if (!chartData) {
chartData = await ChartLatency(service, start, end, "minute", retry); chartData = await ChartLatency(service, start, end, "minute", retry);

View File

@ -108,6 +108,16 @@
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small> <small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" {{if eq .Id 0}}checked{{end}}{{if .VerifySSL.Bool}}checked{{end}}>
<label for="switch-verify-ssl">Verify SSL Certificate for this service</label>
<input type="hidden" name="verify_ssl" id="switch-verify-ssl-value" value="{{if eq .Id 0}}true{{else}}{{if .VerifySSL.Bool}}true{{else}}false{{end}}{{end}}">
</span>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Notifications</label> <label for="order" class="col-sm-4 col-form-label">Notifications</label>
<div class="col-8 mt-1"> <div class="col-8 mt-1">

View File

@ -37,6 +37,20 @@
<input type="text" name="description" class="form-control" value="{{ .Description }}" id="description" placeholder="Great Uptime"> <input type="text" name="description" class="form-control" value="{{ .Description }}" id="description" placeholder="Great Uptime">
</div> </div>
<div class="form-group">
<div class="col-4 col-sm-4 mt-sm-1 mt-0">
<label for="update_notify" class="d-inline d-sm-none">Send Updates only</label>
<label for="update_notify" class="d-none d-sm-block">Send Updates only</label>
<span class="switch">
<input type="checkbox" name="update_notify-option" class="switch" id="switch-update_notify"{{if UPDATENOTIFY}} checked{{end}}>
<label for="switch-update_notify" class="mt-2 mt-sm-0"></label>
</span>
<input type="hidden" name="update_notify" id="switch-update_notify-value" value="{{if UPDATENOTIFY}}true{{else}}false{{end}}">
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<div class="col-8 col-sm-9"> <div class="col-8 col-sm-9">
<label for="domain">Domain</label> <label for="domain">Domain</label>

File diff suppressed because one or more lines are too long

View File

@ -37,6 +37,7 @@ type Core struct {
Version string `gorm:"column:version" json:"version"` Version string `gorm:"column:version" json:"version"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"` MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"` UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
UpdateNotify NullBool `gorm:"column:update_notify;default:false" json:"update_notify,omitempty"`
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"` Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`

View File

@ -34,6 +34,7 @@ type Service struct {
Timeout int `gorm:"default:30;column:timeout" json:"timeout"` Timeout int `gorm:"default:30;column:timeout" json:"timeout"`
Order int `gorm:"default:0;column:order_id" json:"order_id"` Order int `gorm:"default:0;column:order_id" json:"order_id"`
AllowNotifications NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications"` AllowNotifications NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications"`
VerifySSL NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl"`
Public NullBool `gorm:"default:true;column:public" json:"public"` Public NullBool `gorm:"default:true;column:public" json:"public"`
GroupId int `gorm:"default:0;column:group_id" json:"group_id"` GroupId int `gorm:"default:0;column:group_id" json:"group_id"`
Headers NullString `gorm:"column:headers" json:"headers"` Headers NullString `gorm:"column:headers" json:"headers"`
@ -49,6 +50,10 @@ type Service struct {
Checkpoint time.Time `gorm:"-" json:"-"` Checkpoint time.Time `gorm:"-" json:"-"`
SleepDuration time.Duration `gorm:"-" json:"-"` SleepDuration time.Duration `gorm:"-" json:"-"`
LastResponse string `gorm:"-" json:"-"` LastResponse string `gorm:"-" json:"-"`
UserNotified bool `gorm:"-" json:"-"` // True if the User was already notified about a Downtime
UpdateNotify bool `gorm:"-" json:"-"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool`
DownText string `gorm:"-" json:"-"` // Contains the current generated Downtime Text
SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available
LastStatusCode int `gorm:"-" json:"status_code"` LastStatusCode int `gorm:"-" json:"status_code"`
LastOnline time.Time `gorm:"-" json:"last_success"` LastOnline time.Time `gorm:"-" json:"last_success"`
Failures []FailureInterface `gorm:"-" json:"failures,omitempty"` Failures []FailureInterface `gorm:"-" json:"failures,omitempty"`

View File

@ -16,6 +16,7 @@
package utils package utils
import ( import (
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -24,6 +25,7 @@ import (
"io/ioutil" "io/ioutil"
"math" "math"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -274,21 +276,8 @@ func SaveFile(filename string, data []byte) error {
// // body - The body or form data to send with HTTP request // // body - The body or form data to send with HTTP request
// // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds) // // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds)
// // You can use a HTTP Proxy if you HTTP_PROXY environment variable // // You can use a HTTP Proxy if you HTTP_PROXY environment variable
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration) ([]byte, *http.Response, error) { func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool) ([]byte, *http.Response, error) {
var err error var err error
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
ResponseHeaderTimeout: timeout,
TLSHandshakeTimeout: timeout,
Proxy: http.ProxyFromEnvironment,
}
client := &http.Client{
Transport: transport,
Timeout: timeout,
}
var req *http.Request var req *http.Request
if req, err = http.NewRequest(method, url, body); err != nil { if req, err = http.NewRequest(method, url, body); err != nil {
return nil, nil, err return nil, nil, err
@ -310,6 +299,32 @@ func HttpRequest(url, method string, content interface{}, headers []string, body
} }
} }
var resp *http.Response var resp *http.Response
dialer := &net.Dialer{
Timeout: timeout,
KeepAlive: timeout,
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: !verifySSL,
ServerName: req.Host,
},
DisableKeepAlives: true,
ResponseHeaderTimeout: timeout,
TLSHandshakeTimeout: timeout,
Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// redirect all connections to host specified in url
addr = strings.Split(req.URL.Host, ":")[0] + addr[strings.LastIndex(addr, ":"):]
return dialer.DialContext(ctx, network, addr)
},
}
client := &http.Client{
Transport: transport,
Timeout: timeout,
}
if resp, err = client.Do(req); err != nil { if resp, err = client.Do(req); err != nil {
return nil, resp, err return nil, resp, err
} }

View File

@ -1 +1 @@
0.80.63 0.80.64