mirror of https://github.com/statping/statping
				
				
				
			Merge branch 'master' into feature/notify-state-change
						commit
						c1cc5b5179
					
				|  | @ -2,7 +2,7 @@ os: | |||
|   - linux | ||||
| 
 | ||||
| language: go | ||||
| go: 1.12.1 | ||||
| go: 1.13 | ||||
| go_import_path: github.com/hunterlong/statping | ||||
| 
 | ||||
| cache: | ||||
|  |  | |||
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								Makefile
								
								
								
								
							|  | @ -232,7 +232,7 @@ dev-deps: | |||
| 	$(GOGET) github.com/ararog/timeago | ||||
| 	$(GOGET) gopkg.in/natefinch/lumberjack.v2 | ||||
| 	$(GOGET) golang.org/x/crypto/bcrypt | ||||
| 	$(GOGET) github.com/99designs/gqlgen | ||||
| 	$(GOGET) github.com/99designs/gqlgen/... | ||||
| 
 | ||||
| # remove files for a clean compile/build
 | ||||
| clean: | ||||
|  |  | |||
|  | @ -241,7 +241,7 @@ func HelpEcho() { | |||
| func checkGithubUpdates() (githubResponse, error) { | ||||
| 	var gitResp githubResponse | ||||
| 	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 { | ||||
| 		return githubResponse{}, err | ||||
| 	} | ||||
|  |  | |||
|  | @ -211,9 +211,9 @@ func (s *Service) checkHttp(record bool) *Service { | |||
| 	} | ||||
| 
 | ||||
| 	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 { | ||||
| 		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 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)) | ||||
| 	s.CreateHit(hit) | ||||
| 	s.Online = true | ||||
| 	notifier.OnSuccess(s.Service) | ||||
| 	s.Online = true | ||||
| 	s.SuccessNotified = true | ||||
| } | ||||
| 
 | ||||
| // 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)) | ||||
| 	s.CreateFailure(fail) | ||||
| 	s.Online = false | ||||
| 	s.SuccessNotified = false | ||||
| 	s.UpdateNotify = CoreApp.UpdateNotify.Bool | ||||
| 	s.DownText = s.DowntimeText() | ||||
| 	notifier.OnFailure(s.Service, fail.Failure) | ||||
| } | ||||
|  |  | |||
|  | @ -15,7 +15,11 @@ | |||
| 
 | ||||
| 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
 | ||||
| func OnSave(method string) { | ||||
|  | @ -34,12 +38,26 @@ func OnFailure(s *types.Service, f *types.Failure) { | |||
| 	if !s.AllowNotifications.Bool { | ||||
| 		return | ||||
| 	} | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { | ||||
| 			comm.(BasicEvents).OnFailure(s, f) | ||||
| 
 | ||||
| 	// check if User wants to receive every Status Change
 | ||||
| 	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
 | ||||
|  | @ -47,18 +65,26 @@ func OnSuccess(s *types.Service) { | |||
| 	if !s.AllowNotifications.Bool { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// check if User wants to receive every Status Change
 | ||||
| 	if s.UpdateNotify && s.UserNotified { | ||||
| 		s.UserNotified = false | ||||
| 	} | ||||
| 
 | ||||
| 	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 successful %v notification for service %v", notifier.Method, s.Name)) | ||||
| 			comm.(BasicEvents).OnSuccess(s) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // OnNewService is triggered when a new service is created - ServiceEvents interface
 | ||||
| func OnNewService(s *types.Service) { | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -71,6 +97,7 @@ func OnUpdatedService(s *types.Service) { | |||
| 	} | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -83,6 +110,7 @@ func OnDeletedService(s *types.Service) { | |||
| 	} | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -92,6 +120,7 @@ func OnDeletedService(s *types.Service) { | |||
| func OnNewUser(u *types.User) { | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -101,6 +130,7 @@ func OnNewUser(u *types.User) { | |||
| func OnUpdatedUser(u *types.User) { | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -110,6 +140,7 @@ func OnUpdatedUser(u *types.User) { | |||
| func OnDeletedUser(u *types.User) { | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -119,6 +150,7 @@ func OnDeletedUser(u *types.User) { | |||
| func OnUpdatedCore(c *types.Core) { | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { | ||||
| 			utils.Log(1, fmt.Sprintf("Sending updated core notification")) | ||||
| 			comm.(CoreEvents).OnUpdatedCore(c) | ||||
| 		} | ||||
| 	} | ||||
|  | @ -146,6 +178,7 @@ func OnNewNotifier(n *Notification) { | |||
| func OnUpdatedNotifier(n *Notification) { | ||||
| 	for _, comm := range AllCommunications { | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -63,6 +63,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap | |||
| 		"USE_CDN": func() bool { | ||||
| 			return core.CoreApp.UseCdn.Bool | ||||
| 		}, | ||||
| 		"UPDATENOTIFY": func() bool { | ||||
| 			return core.CoreApp.UpdateNotify.Bool | ||||
| 		}, | ||||
| 		"QrAuth": func() string { | ||||
| 			return fmt.Sprintf("statping://setup?domain=%v&api=%v", core.CoreApp.Domain, core.CoreApp.ApiSecret) | ||||
| 		}, | ||||
|  |  | |||
|  | @ -62,11 +62,15 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) { | |||
| 	timeFloat, _ := strconv.ParseFloat(timezone, 10) | ||||
| 	app.Timezone = float32(timeFloat) | ||||
| 
 | ||||
| 	app.UpdateNotify = types.NewNullBool(form.Get("update_notify") == "true") | ||||
| 
 | ||||
| 	app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on") | ||||
| 	core.CoreApp, err = core.UpdateCore(app) | ||||
| 	if err != nil { | ||||
| 		utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error())) | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
 | ||||
| 	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
 | ||||
| // this function is used for the bulk import services feature
 | ||||
| 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) | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | @ -169,6 +173,11 @@ func commaToService(s []string) (*types.Service, error) { | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	verifySsl, err := strconv.ParseBool(s[16]) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	newService := &types.Service{ | ||||
| 		Name:               s[0], | ||||
| 		Domain:             s[1], | ||||
|  | @ -185,6 +194,7 @@ func commaToService(s []string) (*types.Service, error) { | |||
| 		GroupId:            int(utils.ToInt(s[13])), | ||||
| 		Headers:            types.NewNullString(s[14]), | ||||
| 		Permalink:          types.NewNullString(s[15]), | ||||
| 		VerifySSL:          types.NewNullBool(verifySsl), | ||||
| 	} | ||||
| 
 | ||||
| 	return newService, nil | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ func init() { | |||
| // Send will send a HTTP Post to the discord API. It accepts type: []byte
 | ||||
| func (u *discord) Send(msg interface{}) error { | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
|  | @ -75,9 +75,14 @@ func (u *discord) OnFailure(s *types.Service, f *types.Failure) { | |||
| 
 | ||||
| // OnSuccess will trigger successful service
 | ||||
| func (u *discord) OnSuccess(s *types.Service) { | ||||
| 	if !s.Online { | ||||
| 	if !s.Online || !s.SuccessNotified { | ||||
| 		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) | ||||
| 	} | ||||
| } | ||||
|  | @ -93,7 +98,7 @@ func (u *discord) OnSave() error { | |||
| func (u *discord) OnTest() error { | ||||
| 	outError := errors.New("Incorrect discord URL, please confirm URL is correct") | ||||
| 	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) == "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  |  | |||
|  | @ -24,7 +24,6 @@ import ( | |||
| 	"github.com/hunterlong/statping/types" | ||||
| 	"github.com/hunterlong/statping/utils" | ||||
| 	"html/template" | ||||
| 	"net/smtp" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
|  | @ -199,11 +198,17 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) { | |||
| 
 | ||||
| // OnSuccess will trigger successful 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)) | ||||
| 		email := &emailOutgoing{ | ||||
| 			To:       u.Var2, | ||||
| 			Subject:  fmt.Sprintf("Service %v is Back Online", s.Name), | ||||
| 			Subject:  msg, | ||||
| 			Template: mainEmailTemplate, | ||||
| 			Data:     interface{}(s), | ||||
| 			From:     u.Var1, | ||||
|  | @ -225,24 +230,6 @@ func (u *email) OnSave() error { | |||
| 
 | ||||
| // OnTest triggers when this notifier has been saved
 | ||||
| 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{ | ||||
| 		Id:             1, | ||||
| 		Name:           "Example Service", | ||||
|  | @ -261,11 +248,10 @@ func (u *email) OnTest() error { | |||
| 		To:       u.Var2, | ||||
| 		Subject:  fmt.Sprintf("Service %v is Back Online", testService.Name), | ||||
| 		Template: mainEmailTemplate, | ||||
| 		Data:     interface{}(testService), | ||||
| 		Data:     testService, | ||||
| 		From:     u.Var1, | ||||
| 	} | ||||
| 	err = u.Send(email) | ||||
| 	return err | ||||
| 	return u.dialSend(email) | ||||
| } | ||||
| 
 | ||||
| func (u *email) dialSend(email *emailOutgoing) error { | ||||
|  |  | |||
|  | @ -62,7 +62,7 @@ func (u *lineNotifier) Send(msg interface{}) error { | |||
| 	v := url.Values{} | ||||
| 	v.Set("message", message) | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
|  | @ -78,16 +78,22 @@ func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) { | |||
| 
 | ||||
| // OnSuccess will trigger successful 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)) | ||||
| 		msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) | ||||
| 		u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // OnSave triggers when this notifier has been saved
 | ||||
| func (u *lineNotifier) OnSave() error { | ||||
| 	utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) | ||||
| 	// Do updating stuff here
 | ||||
| 	msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method) | ||||
| 	utils.Log(1, msg) | ||||
| 	u.AddQueue("saved", message) | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -105,10 +105,16 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) { | |||
| // OnSuccess will trigger successful service
 | ||||
| func (u *mobilePush) OnSuccess(s *types.Service) { | ||||
| 	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)) | ||||
| 		msg := &pushArray{ | ||||
| 			Message: fmt.Sprintf("Your service '%v' is back online!", s.Name), | ||||
| 			Message: msgStr, | ||||
| 			Title:   "Service Online", | ||||
| 			Topic:   mobileIdentifier, | ||||
| 			Data:    data, | ||||
|  | @ -176,7 +182,7 @@ func pushRequest(msg *pushArray) ([]byte, error) { | |||
| 		return nil, err | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ import ( | |||
| 
 | ||||
| const ( | ||||
| 	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" } ] }` | ||||
| 	slackText       = `{"text":"{{.}}"}` | ||||
| ) | ||||
|  | @ -79,12 +79,13 @@ type slackMessage struct { | |||
| 	Service  *types.Service | ||||
| 	Template string | ||||
| 	Time     int64 | ||||
| 	Issue    string | ||||
| } | ||||
| 
 | ||||
| // Send will send a HTTP Post to the slack webhooker API. It accepts type: string
 | ||||
| func (u *slack) Send(msg interface{}) error { | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
|  | @ -93,7 +94,7 @@ func (u *slack) Select() *notifier.Notification { | |||
| } | ||||
| 
 | ||||
| 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" { | ||||
| 		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, | ||||
| 		Template: failingTemplate, | ||||
| 		Time:     time.Now().Unix(), | ||||
| 		Issue:    f.Issue, | ||||
| 	} | ||||
| 	parseSlackMessage(s.Id, failingTemplate, message) | ||||
| } | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ func (u *telegram) Send(msg interface{}) error { | |||
| 	v.Set("text", message) | ||||
| 	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) | ||||
| 	if !success { | ||||
|  | @ -97,9 +97,14 @@ func (u *telegram) OnFailure(s *types.Service, f *types.Failure) { | |||
| 
 | ||||
| // OnSuccess will trigger successful service
 | ||||
| func (u *telegram) OnSuccess(s *types.Service) { | ||||
| 	if !s.Online { | ||||
| 	if !s.Online || !s.SuccessNotified { | ||||
| 		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) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ func (u *twilio) Send(msg interface{}) error { | |||
| 	v.Set("Body", message) | ||||
| 	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) | ||||
| 	if !success { | ||||
| 		errorOut := twilioError(contents) | ||||
|  | @ -107,9 +107,14 @@ func (u *twilio) OnFailure(s *types.Service, f *types.Failure) { | |||
| 
 | ||||
| // OnSuccess will trigger successful service
 | ||||
| func (u *twilio) OnSuccess(s *types.Service) { | ||||
| 	if !s.Online { | ||||
| 	if !s.Online || !s.SuccessNotified { | ||||
| 		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) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -7,66 +7,54 @@ | |||
| /*   Mobile Service Container   */ | ||||
| HTML, BODY { | ||||
|   background-color: #fcfcfc; | ||||
|   padding-bottom: 10px; | ||||
| } | ||||
|   padding-bottom: 10px; } | ||||
| 
 | ||||
| .container { | ||||
|   padding-top: 20px; | ||||
|   padding-bottom: 25px; | ||||
|   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 { | ||||
|   color: #464646; | ||||
| } | ||||
|   color: #464646; } | ||||
| 
 | ||||
| .header-desc { | ||||
|   color: #939393; | ||||
| } | ||||
|   color: #939393; } | ||||
| 
 | ||||
| .btn { | ||||
|   border-radius: 0.2rem; | ||||
| } | ||||
|   border-radius: 0.2rem; } | ||||
| 
 | ||||
| .online_list .badge { | ||||
|   margin-top: 0.2rem; | ||||
| } | ||||
|   margin-top: 0.2rem; } | ||||
| 
 | ||||
| .navbar { | ||||
|   margin-bottom: 30px; | ||||
| } | ||||
|   margin-bottom: 30px; } | ||||
| 
 | ||||
| .btn-sm { | ||||
|   line-height: 1.3; | ||||
|   font-size: 0.75rem; | ||||
| } | ||||
|   font-size: 0.75rem; } | ||||
| 
 | ||||
| .view_service_btn { | ||||
|   position: absolute; | ||||
|   bottom: -40px; | ||||
|   right: 40px; | ||||
| } | ||||
|   right: 40px; } | ||||
| 
 | ||||
| .service_lower_info { | ||||
|   position: absolute; | ||||
|   bottom: -40px; | ||||
|   left: 40px; | ||||
|   color: #d1ffca; | ||||
|   font-size: 0.85rem; | ||||
| } | ||||
|   font-size: 0.85rem; } | ||||
| 
 | ||||
| .lg_number { | ||||
|   font-size: 2.3rem; | ||||
|   font-weight: bold; | ||||
|   display: block; | ||||
|   color: #4f4f4f; | ||||
| } | ||||
|   color: #4f4f4f; } | ||||
| 
 | ||||
| .stats_area { | ||||
|   text-align: center; | ||||
|   color: #a5a5a5; | ||||
| } | ||||
|   color: #a5a5a5; } | ||||
| 
 | ||||
| .lower_canvas { | ||||
|   height: 3.4rem; | ||||
|  | @ -74,101 +62,82 @@ HTML, BODY { | |||
|   background-color: #48d338; | ||||
|   padding: 15px 10px; | ||||
|   margin-left: 0px !important; | ||||
|   margin-right: 0px !important; | ||||
| } | ||||
|   margin-right: 0px !important; } | ||||
| 
 | ||||
| .lower_canvas SPAN { | ||||
|   font-size: 1rem; | ||||
|   color: #fff; | ||||
| } | ||||
|   color: #fff; } | ||||
| 
 | ||||
| .footer { | ||||
|   text-decoration: none; | ||||
|   margin-top: 20px; | ||||
| } | ||||
|   margin-top: 20px; } | ||||
| 
 | ||||
| .footer A { | ||||
|   color: #8d8d8d; | ||||
|   text-decoration: none; | ||||
| } | ||||
|   text-decoration: none; } | ||||
| 
 | ||||
| .footer A:HOVER { | ||||
|   color: #6d6d6d; | ||||
| } | ||||
|   color: #6d6d6d; } | ||||
| 
 | ||||
| .badge { | ||||
|   color: white; | ||||
|   border-radius: 0.2rem; | ||||
| } | ||||
|   border-radius: 0.2rem; } | ||||
| 
 | ||||
| .btn-group { | ||||
|   height: 25px; | ||||
| } | ||||
| .btn-group A { | ||||
|   padding: 0.1rem 0.75rem; | ||||
|   font-size: 0.8rem; | ||||
| } | ||||
|   height: 25px; } | ||||
|   .btn-group A { | ||||
|     padding: 0.1rem .75rem; | ||||
|     font-size: 0.8rem; } | ||||
| 
 | ||||
| .card-body .badge { | ||||
|   color: #fff; | ||||
| } | ||||
|   color: #fff; } | ||||
| 
 | ||||
| .nav-pills .nav-link { | ||||
|   border-radius: 0.2rem; | ||||
| } | ||||
|   border-radius: 0.2rem; } | ||||
| 
 | ||||
| .form-control { | ||||
|   border-radius: 0.2rem; | ||||
| } | ||||
|   border-radius: 0.2rem; } | ||||
| 
 | ||||
| .card { | ||||
|   background-color: #ffffff; | ||||
|   border: 1px solid rgba(0, 0, 0, 0.125); | ||||
| } | ||||
|   border: 1px solid rgba(0, 0, 0, 0.125); } | ||||
| 
 | ||||
| .card-body { | ||||
|   overflow: hidden; | ||||
| } | ||||
|   overflow: hidden; } | ||||
| 
 | ||||
| .card-body H4 A { | ||||
|   color: #444444; | ||||
|   text-decoration: none; | ||||
| } | ||||
|   text-decoration: none; } | ||||
| 
 | ||||
| .chart-container { | ||||
|   position: relative; | ||||
|   height: 170px; | ||||
|   width: 100%; | ||||
|   overflow: hidden; | ||||
| } | ||||
|   overflow: hidden; } | ||||
| 
 | ||||
| .service-chart-container { | ||||
|   position: relative; | ||||
|   height: 400px; | ||||
|   width: 100%; | ||||
| } | ||||
|   width: 100%; } | ||||
| 
 | ||||
| .service-chart-heatmap { | ||||
|   position: relative; | ||||
|   height: 300px; | ||||
|   width: 100%; | ||||
| } | ||||
|   width: 100%; } | ||||
| 
 | ||||
| .inputTags-field { | ||||
|   border: 0; | ||||
|   background-color: transparent; | ||||
|   padding-top: 0.13rem; | ||||
| } | ||||
|   padding-top: .13rem; } | ||||
| 
 | ||||
| input.inputTags-field:focus { | ||||
|   outline-width: 0; | ||||
| } | ||||
|   outline-width: 0; } | ||||
| 
 | ||||
| .inputTags-list { | ||||
|   display: block; | ||||
|   width: 100%; | ||||
|   min-height: calc(2.25rem + 2px); | ||||
|   padding: 0.2rem 0.35rem; | ||||
|   padding: .2rem .35rem; | ||||
|   font-size: 1rem; | ||||
|   font-weight: 400; | ||||
|   line-height: 1.5; | ||||
|  | @ -176,9 +145,8 @@ input.inputTags-field:focus { | |||
|   background-color: #fff; | ||||
|   background-clip: padding-box; | ||||
|   border: 1px solid #ced4da; | ||||
|   border-radius: 0.25rem; | ||||
|   transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; | ||||
| } | ||||
|   border-radius: .25rem; | ||||
|   transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; } | ||||
| 
 | ||||
| .inputTags-item { | ||||
|   background-color: #3aba39; | ||||
|  | @ -186,81 +154,63 @@ input.inputTags-field:focus { | |||
|   padding: 5px 8px; | ||||
|   font-size: 10pt; | ||||
|   color: white; | ||||
|   border-radius: 4px; | ||||
| } | ||||
|   border-radius: 4px; } | ||||
| 
 | ||||
| .inputTags-item .close-item { | ||||
|   margin-left: 6px; | ||||
|   font-size: 13pt; | ||||
|   font-weight: bold; | ||||
|   cursor: pointer; | ||||
| } | ||||
|   cursor: pointer; } | ||||
| 
 | ||||
| .btn-primary { | ||||
|   background-color: #3e9bff; | ||||
|   border-color: #006fe6; | ||||
|   color: white; | ||||
| } | ||||
| .btn-primary.dyn-dark { | ||||
|   background-color: #32a825 !important; | ||||
|   border-color: #2c9320 !important; | ||||
| } | ||||
| .btn-primary.dyn-light { | ||||
|   background-color: #75de69 !important; | ||||
|   border-color: #88e37e !important; | ||||
| } | ||||
|   color: white; } | ||||
|   .btn-primary.dyn-dark { | ||||
|     background-color: #32a825 !important; | ||||
|     border-color: #2c9320 !important; } | ||||
|   .btn-primary.dyn-light { | ||||
|     background-color: #75de69 !important; | ||||
|     border-color: #88e37e !important; } | ||||
| 
 | ||||
| .btn-success { | ||||
|   background-color: #47d337; | ||||
| } | ||||
| .btn-success.dyn-dark { | ||||
|   background-color: #32a825 !important; | ||||
|   border-color: #2c9320 !important; | ||||
| } | ||||
| .btn-success.dyn-light { | ||||
|   background-color: #75de69 !important; | ||||
|   border-color: #88e37e !important; | ||||
| } | ||||
|   background-color: #47d337; } | ||||
|   .btn-success.dyn-dark { | ||||
|     background-color: #32a825 !important; | ||||
|     border-color: #2c9320 !important; } | ||||
|   .btn-success.dyn-light { | ||||
|     background-color: #75de69 !important; | ||||
|     border-color: #88e37e !important; } | ||||
| 
 | ||||
| .btn-danger { | ||||
|   background-color: #dd3545; | ||||
| } | ||||
| .btn-danger.dyn-dark { | ||||
|   background-color: #b61f2d !important; | ||||
|   border-color: #a01b28 !important; | ||||
| } | ||||
| .btn-danger.dyn-light { | ||||
|   background-color: #e66975 !important; | ||||
|   border-color: #e97f89 !important; | ||||
| } | ||||
|   background-color: #dd3545; } | ||||
|   .btn-danger.dyn-dark { | ||||
|     background-color: #b61f2d !important; | ||||
|     border-color: #a01b28 !important; } | ||||
|   .btn-danger.dyn-light { | ||||
|     background-color: #e66975 !important; | ||||
|     border-color: #e97f89 !important; } | ||||
| 
 | ||||
| .bg-success { | ||||
|   background-color: #47d337 !important; | ||||
| } | ||||
|   background-color: #47d337 !important; } | ||||
| 
 | ||||
| .bg-danger { | ||||
|   background-color: #dd3545 !important; | ||||
| } | ||||
|   background-color: #dd3545 !important; } | ||||
| 
 | ||||
| .bg-success .dyn-dark { | ||||
|   background-color: #35b027 !important; | ||||
| } | ||||
|   background-color: #35b027 !important; } | ||||
| 
 | ||||
| .bg-danger .dyn-dark { | ||||
|   background-color: #bf202f !important; | ||||
| } | ||||
|   background-color: #bf202f !important; } | ||||
| 
 | ||||
| .nav-pills .nav-link.active, .nav-pills .show > .nav-link { | ||||
|   background-color: #13a00d; | ||||
| } | ||||
|   background-color: #13a00d; } | ||||
| 
 | ||||
| .nav-pills A { | ||||
|   color: #424242; | ||||
| } | ||||
|   color: #424242; } | ||||
| 
 | ||||
| .nav-pills I { | ||||
|   margin-right: 10px; | ||||
| } | ||||
|   margin-right: 10px; } | ||||
| 
 | ||||
| .CodeMirror { | ||||
|   /* Bootstrap Settings */ | ||||
|  | @ -280,26 +230,23 @@ input.inputTags-field:focus { | |||
|   border: 1px solid #ccc; | ||||
|   border-radius: 4px; | ||||
|   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 */ | ||||
|   font-family: monospace; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   height: 80vh; | ||||
| } | ||||
|   height: 80vh; } | ||||
| 
 | ||||
| .CodeMirror-focused { | ||||
|   /* Bootstrap Settings */ | ||||
|   border-color: #66afe9; | ||||
|   outline: 0; | ||||
|   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 { | ||||
|   font-size: 1rem; | ||||
|   position: relative; | ||||
| } | ||||
|   position: relative; } | ||||
| 
 | ||||
| .switch input { | ||||
|   position: absolute; | ||||
|  | @ -310,8 +257,7 @@ input.inputTags-field:focus { | |||
|   clip: rect(0 0 0 0); | ||||
|   clip-path: inset(50%); | ||||
|   overflow: hidden; | ||||
|   padding: 0; | ||||
| } | ||||
|   padding: 0; } | ||||
| 
 | ||||
| .switch input + label { | ||||
|   position: relative; | ||||
|  | @ -324,26 +270,23 @@ input.inputTags-field:focus { | |||
|   outline: none; | ||||
|   user-select: none; | ||||
|   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::after { | ||||
|   content: ""; | ||||
|   content: ''; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: calc(calc(2.375rem * .8) * 2); | ||||
|   bottom: 0; | ||||
|   display: block; | ||||
| } | ||||
|   display: block; } | ||||
| 
 | ||||
| .switch input + label::before { | ||||
|   right: 0; | ||||
|   background-color: #dee2e6; | ||||
|   border-radius: calc(2.375rem * .8); | ||||
|   transition: 0.2s all; | ||||
| } | ||||
|   transition: 0.2s all; } | ||||
| 
 | ||||
| .switch input + label::after { | ||||
|   top: 2px; | ||||
|  | @ -352,137 +295,105 @@ input.inputTags-field:focus { | |||
|   height: calc(calc(2.375rem * .8) - calc(2px * 2)); | ||||
|   border-radius: 50%; | ||||
|   background-color: white; | ||||
|   transition: 0.2s all; | ||||
| } | ||||
|   transition: 0.2s all; } | ||||
| 
 | ||||
| .switch input:checked + label::before { | ||||
|   background-color: #08d; | ||||
| } | ||||
|   background-color: #08d; } | ||||
| 
 | ||||
| .switch input:checked + label::after { | ||||
|   margin-left: calc(2.375rem * .8); | ||||
| } | ||||
|   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); | ||||
| } | ||||
|   box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); } | ||||
| 
 | ||||
| .switch input:disabled + label { | ||||
|   color: #868e96; | ||||
|   cursor: not-allowed; | ||||
| } | ||||
|   cursor: not-allowed; } | ||||
| 
 | ||||
| .switch input:disabled + label::before { | ||||
|   background-color: #e9ecef; | ||||
| } | ||||
|   background-color: #e9ecef; } | ||||
| 
 | ||||
| .switch.switch-sm { | ||||
|   font-size: 0.875rem; | ||||
| } | ||||
|   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); | ||||
| } | ||||
|   text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); } | ||||
| 
 | ||||
| .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 { | ||||
|   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 { | ||||
|   margin-left: calc(1.9375rem * .8); | ||||
| } | ||||
|   margin-left: calc(1.9375rem * .8); } | ||||
| 
 | ||||
| .switch.switch-lg { | ||||
|   font-size: 1.25rem; | ||||
| } | ||||
|   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); | ||||
| } | ||||
|   text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); } | ||||
| 
 | ||||
| .switch.switch-lg input + label::before { | ||||
|   width: calc(calc(3rem * .8) * 2); | ||||
| } | ||||
|   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)); | ||||
| } | ||||
|   height: calc(calc(3rem * .8) - calc(2px * 2)); } | ||||
| 
 | ||||
| .switch.switch-lg input:checked + label::after { | ||||
|   margin-left: calc(3rem * .8); | ||||
| } | ||||
|   margin-left: calc(3rem * .8); } | ||||
| 
 | ||||
| .switch + .switch { | ||||
|   margin-left: 1rem; | ||||
| } | ||||
|   margin-left: 1rem; } | ||||
| 
 | ||||
| @keyframes pulse_animation { | ||||
|   0% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
|     transform: scale(1); } | ||||
|   30% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
|     transform: scale(1); } | ||||
|   40% { | ||||
|     transform: scale(1.02); | ||||
|   } | ||||
|     transform: scale(1.02); } | ||||
|   50% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
|     transform: scale(1); } | ||||
|   60% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
|     transform: scale(1); } | ||||
|   70% { | ||||
|     transform: scale(1.05); | ||||
|   } | ||||
|     transform: scale(1.05); } | ||||
|   80% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
|     transform: scale(1); } | ||||
|   100% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
| } | ||||
|     transform: scale(1); } } | ||||
| .pulse { | ||||
|   animation-name: pulse_animation; | ||||
|   animation-duration: 1500ms; | ||||
|   transform-origin: 70% 70%; | ||||
|   animation-iteration-count: infinite; | ||||
|   animation-timing-function: linear; | ||||
| } | ||||
|   animation-timing-function: linear; } | ||||
| 
 | ||||
| @keyframes glow-grow { | ||||
|   0% { | ||||
|     opacity: 0; | ||||
|     transform: scale(1); | ||||
|   } | ||||
|     transform: scale(1); } | ||||
|   80% { | ||||
|     opacity: 1; | ||||
|   } | ||||
|     opacity: 1; } | ||||
|   100% { | ||||
|     transform: scale(2); | ||||
|     opacity: 0; | ||||
|   } | ||||
| } | ||||
|     opacity: 0; } } | ||||
| .pulse-glow { | ||||
|   animation-name: glow-grown; | ||||
|   animation-duration: 100ms; | ||||
|   transform-origin: 70% 30%; | ||||
|   animation-iteration-count: infinite; | ||||
|   animation-timing-function: linear; | ||||
| } | ||||
|   animation-timing-function: linear; } | ||||
| 
 | ||||
| .pulse-glow:before, | ||||
| .pulse-glow:after { | ||||
|  | @ -494,12 +405,10 @@ input.inputTags-field:focus { | |||
|   right: 2.15rem; | ||||
|   border-radius: 0; | ||||
|   box-shadow: 0 0 6px #47d337; | ||||
|   animation: glow-grow 2s ease-out infinite; | ||||
| } | ||||
|   animation: glow-grow 2s ease-out infinite; } | ||||
| 
 | ||||
| .sortable_drag { | ||||
|   background-color: #0000000f; | ||||
| } | ||||
|   background-color: #0000000f; } | ||||
| 
 | ||||
| .drag_icon { | ||||
|   cursor: move; | ||||
|  | @ -513,139 +422,112 @@ input.inputTags-field:focus { | |||
|   margin-right: 5px; | ||||
|   margin-left: -10px; | ||||
|   text-align: center; | ||||
|   color: #b1b1b1; | ||||
| } | ||||
|   color: #b1b1b1; } | ||||
| 
 | ||||
| /* (Optional) Apply a "closed-hand" cursor during drag operation. */ | ||||
| .drag_icon:active { | ||||
|   cursor: grabbing; | ||||
|   cursor: -moz-grabbing; | ||||
|   cursor: -webkit-grabbing; | ||||
| } | ||||
|   cursor: -webkit-grabbing; } | ||||
| 
 | ||||
| .switch_btn { | ||||
|   float: right; | ||||
|   margin: -1px 0px 0px 0px; | ||||
|   display: block; | ||||
| } | ||||
|   display: block; } | ||||
| 
 | ||||
| #start_container { | ||||
|   position: absolute; | ||||
|   z-index: 99999; | ||||
|   margin-top: 20px; | ||||
| } | ||||
|   margin-top: 20px; } | ||||
| 
 | ||||
| #end_container { | ||||
|   position: absolute; | ||||
|   z-index: 99999; | ||||
|   margin-top: 20px; | ||||
|   right: 0; | ||||
| } | ||||
|   right: 0; } | ||||
| 
 | ||||
| .pointer { | ||||
|   cursor: pointer; | ||||
| } | ||||
|   cursor: pointer; } | ||||
| 
 | ||||
| .jumbotron { | ||||
|   background-color: white; | ||||
| } | ||||
|   background-color: white; } | ||||
| 
 | ||||
| .toggle-service { | ||||
|   font-size: 18pt; | ||||
|   float: left; | ||||
|   margin: 2px 3px 0 0; | ||||
|   cursor: pointer; | ||||
| } | ||||
|   cursor: pointer; } | ||||
| 
 | ||||
| @media (max-width: 767px) { | ||||
|   HTML, BODY { | ||||
|     background-color: #fcfcfc; | ||||
|   } | ||||
|     background-color: #fcfcfc; } | ||||
| 
 | ||||
|   .sm-container { | ||||
|     margin-top: 0px !important; | ||||
|     padding: 0 !important; | ||||
|   } | ||||
|     padding: 0 !important; } | ||||
| 
 | ||||
|   .list-group-item H5 { | ||||
|     font-size: 0.9rem; | ||||
|   } | ||||
|     font-size: 0.9rem; } | ||||
| 
 | ||||
|   .container { | ||||
|     padding: 0px !important; | ||||
|     padding-top: 15px !important; | ||||
|   } | ||||
|     padding-top: 15px !important; } | ||||
| 
 | ||||
|   .group_header { | ||||
|     margin-left: 15px; | ||||
|   } | ||||
|     margin-left: 15px; } | ||||
| 
 | ||||
|   .navbar { | ||||
|     margin-left: 0px; | ||||
|     margin-top: 0px; | ||||
|     width: 100%; | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
|     margin-bottom: 0; } | ||||
| 
 | ||||
|   .btn-sm { | ||||
|     line-height: 0.9rem; | ||||
|     font-size: 0.65rem; | ||||
|   } | ||||
|     font-size: 0.65rem; } | ||||
| 
 | ||||
|   .full-col-12 { | ||||
|     padding-left: 0px; | ||||
|     padding-right: 0px; | ||||
|   } | ||||
|     padding-right: 0px; } | ||||
| 
 | ||||
|   .card { | ||||
|     border: 0; | ||||
|     border-radius: 0rem; | ||||
|     padding: 0; | ||||
|     background-color: #ffffff; | ||||
|   } | ||||
|     background-color: #ffffff; } | ||||
| 
 | ||||
|   .card-body { | ||||
|     font-size: 10pt; | ||||
|     padding: 0px 10px; | ||||
|   } | ||||
|     padding: 0px 10px; } | ||||
| 
 | ||||
|   .lg_number { | ||||
|     font-size: 7.8vw; | ||||
|   } | ||||
|     font-size: 7.8vw; } | ||||
| 
 | ||||
|   .stats_area { | ||||
|     margin-top: 1.5rem !important; | ||||
|     margin-bottom: 1.5rem !important; | ||||
|   } | ||||
|     margin-bottom: 1.5rem !important; } | ||||
| 
 | ||||
|   .stats_area .col-4 { | ||||
|     padding-left: 0; | ||||
|     padding-right: 0; | ||||
|     font-size: 0.6rem; | ||||
|   } | ||||
|     font-size: 0.6rem; } | ||||
| 
 | ||||
|   .list-group-item { | ||||
|     border-top: 1px solid #e4e4e4; | ||||
|     border: 0px; | ||||
|   } | ||||
|     border: 0px; } | ||||
| 
 | ||||
|   .list-group-item:first-child { | ||||
|     border-top-left-radius: 0; | ||||
|     border-top-right-radius: 0; | ||||
|   } | ||||
|     border-top-right-radius: 0; } | ||||
| 
 | ||||
|   .list-group-item:last-child { | ||||
|     border-bottom-right-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
|     border-bottom-left-radius: 0; } | ||||
| 
 | ||||
|   .list-group-item P { | ||||
|     font-size: 0.7rem; | ||||
|   } | ||||
|     font-size: 0.7rem; } | ||||
| 
 | ||||
|   .service-chart-container { | ||||
|     height: 200px; | ||||
|   } | ||||
| } | ||||
|     height: 200px; } } | ||||
| 
 | ||||
| /*# sourceMappingURL=base.css.map */ | ||||
|  |  | |||
|  | @ -90,7 +90,7 @@ $('.toggle-service').on('click',function(e) { | |||
| 	let obj = $(this); | ||||
| 	let serviceId = obj.attr("data-id"); | ||||
| 	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) { | ||||
| 		$.ajax({ | ||||
| 			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) { | ||||
| 		if (!chart.el) { | ||||
| 			return | ||||
| 		} | ||||
|     let chartData = await ChartLatency(service, start, end, group, retry); | ||||
|     if (!chartData) { | ||||
|         chartData = await ChartLatency(service, start, end, "minute", retry); | ||||
|  |  | |||
|  | @ -108,6 +108,16 @@ | |||
|             <small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small> | ||||
|         </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"> | ||||
|         <label for="order" class="col-sm-4 col-form-label">Notifications</label> | ||||
|         <div class="col-8 mt-1"> | ||||
|  |  | |||
|  | @ -37,6 +37,20 @@ | |||
|                                 <input type="text" name="description" class="form-control" value="{{ .Description }}" id="description" placeholder="Great Uptime"> | ||||
|                             </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="col-8 col-sm-9"> | ||||
|                                     <label for="domain">Domain</label> | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -37,6 +37,7 @@ type Core struct { | |||
| 	Version       string             `gorm:"column:version" json:"version"` | ||||
| 	MigrationId   int64              `gorm:"column:migration_id" json:"migration_id,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"` | ||||
| 	CreatedAt     time.Time          `gorm:"column:created_at" json:"created_at"` | ||||
| 	UpdatedAt     time.Time          `gorm:"column:updated_at" json:"updated_at"` | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ type Service struct { | |||
| 	Timeout            int                `gorm:"default:30;column:timeout" json:"timeout"` | ||||
| 	Order              int                `gorm:"default:0;column:order_id" json:"order_id"` | ||||
| 	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"` | ||||
| 	GroupId            int                `gorm:"default:0;column:group_id" json:"group_id"` | ||||
| 	Headers            NullString         `gorm:"column:headers" json:"headers"` | ||||
|  | @ -49,6 +50,10 @@ type Service struct { | |||
| 	Checkpoint         time.Time          `gorm:"-" json:"-"` | ||||
| 	SleepDuration      time.Duration      `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"` | ||||
| 	LastOnline         time.Time          `gorm:"-" json:"last_success"` | ||||
| 	Failures           []FailureInterface `gorm:"-" json:"failures,omitempty"` | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| package utils | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | @ -24,6 +25,7 @@ import ( | |||
| 	"io/ioutil" | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
|  | @ -274,21 +276,8 @@ func SaveFile(filename string, data []byte) error { | |||
| // // body - The body or form data to send with HTTP request
 | ||||
| // // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds)
 | ||||
| // // 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 | ||||
| 	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 | ||||
| 	if req, err = http.NewRequest(method, url, body); err != nil { | ||||
| 		return nil, nil, err | ||||
|  | @ -310,6 +299,32 @@ func HttpRequest(url, method string, content interface{}, headers []string, body | |||
| 		} | ||||
| 	} | ||||
| 	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 { | ||||
| 		return nil, resp, err | ||||
| 	} | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| 0.80.63 | ||||
| 0.80.64 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Hunter Long
						Hunter Long