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
language: go
go: 1.12.1
go: 1.13
go_import_path: github.com/hunterlong/statping
cache:

View File

@ -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:

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
},

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 */

View File

@ -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);

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>
</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">

View File

@ -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

View File

@ -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"`

View File

@ -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"`

View File

@ -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
}

View File

@ -1 +1 @@
0.80.63
0.80.64