From 9f93d9c2d411b786675b203d1421f7d9ca790d90 Mon Sep 17 00:00:00 2001 From: Valerio Cestrone Date: Wed, 20 Feb 2019 15:31:53 +0000 Subject: [PATCH 001/100] Update main.js this allows the chart to be shown even if the API response is null --- source/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/js/main.js b/source/js/main.js index 87e96123..a8b1da44 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -119,7 +119,7 @@ async function RenderChart(chart, service, start=0, end=9999999999, group="hour" } chart.render(); chart.updateSeries([{ - data: chartData + data: chartData || [] }]); } From 30a1216f23e9b670e73900b97040f6a99a547476 Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Fri, 16 Aug 2019 02:26:36 +0300 Subject: [PATCH 002/100] remove .Online from notifiers and toggle service.Online when needed. fixes #118 --- core/checker.go | 6 +- core/notifier/events.go | 2 + core/notifier/notifiers.go | 1 - handlers/graphql/generated.go | 2636 +++++++++++++++++++++++++-------- notifiers/command.go | 4 +- notifiers/command_test.go | 6 +- notifiers/discord.go | 4 +- notifiers/discord_test.go | 4 +- notifiers/email.go | 4 +- notifiers/email_test.go | 4 +- notifiers/line_notify.go | 4 +- notifiers/mobile.go | 4 +- notifiers/mobile_test.go | 6 +- notifiers/slack.go | 4 +- notifiers/slack_test.go | 6 +- notifiers/telegram.go | 4 +- notifiers/telegram_test.go | 4 +- notifiers/twilio.go | 4 +- notifiers/twilio_test.go | 4 +- notifiers/webhook.go | 4 +- notifiers/webhook_test.go | 4 +- source/css/base.css | 384 ++--- source/wiki.go | 6 +- 23 files changed, 2151 insertions(+), 958 deletions(-) diff --git a/core/checker.go b/core/checker.go index 690a4282..7f6ce2fd 100644 --- a/core/checker.go +++ b/core/checker.go @@ -243,7 +243,6 @@ func (s *Service) checkHttp(record bool) *Service { } return s } - s.Online = true if record { recordSuccess(s) } @@ -252,7 +251,6 @@ func (s *Service) checkHttp(record bool) *Service { // recordSuccess will create a new 'hit' record in the database for a successful/online service func recordSuccess(s *Service) { - s.Online = true s.LastOnline = utils.Timezoner(time.Now().UTC(), CoreApp.Timezone) hit := &types.Hit{ Service: s.Id, @@ -263,11 +261,11 @@ 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) notifier.OnSuccess(s.Service) + s.Online = true } // recordFailure will create a new 'Failure' record in the database for a offline service func recordFailure(s *Service, issue string) { - s.Online = false fail := &Failure{&types.Failure{ Service: s.Id, Issue: issue, @@ -278,4 +276,6 @@ 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) notifier.OnFailure(s.Service, fail.Failure) + s.Online = false + } diff --git a/core/notifier/events.go b/core/notifier/events.go index 8397f828..3f0fdd1c 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -39,6 +39,7 @@ func OnFailure(s *types.Service, f *types.Failure) { comm.(BasicEvents).OnFailure(s, f) } } + } // OnSuccess will be triggered when a service is successful - BasicEvents interface @@ -51,6 +52,7 @@ func OnSuccess(s *types.Service) { comm.(BasicEvents).OnSuccess(s) } } + } // OnNewService is triggered when a new service is created - ServiceEvents interface diff --git a/core/notifier/notifiers.go b/core/notifier/notifiers.go index 0dff4b9f..f0924fa2 100644 --- a/core/notifier/notifiers.go +++ b/core/notifier/notifiers.go @@ -62,7 +62,6 @@ type Notification struct { Delay time.Duration `gorm:"-" json:"delay,string"` Queue []*QueueData `gorm:"-" json:"-"` Running chan bool `gorm:"-" json:"-"` - Online bool `gorm:"-" json:"online"` testable bool `gorm:"-" json:"testable"` } diff --git a/handlers/graphql/generated.go b/handlers/graphql/generated.go index f695d553..71aa3124 100644 --- a/handlers/graphql/generated.go +++ b/handlers/graphql/generated.go @@ -8,6 +8,7 @@ import ( "errors" "strconv" "sync" + "sync/atomic" "time" "github.com/99designs/gqlgen/graphql" @@ -237,350 +238,350 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in _ = ec switch typeName + "." + field { - case "Checkin.ApiKey": + case "Checkin.api_key": if e.complexity.Checkin.ApiKey == nil { break } return e.complexity.Checkin.ApiKey(childComplexity), true - case "Checkin.CreatedAt": + case "Checkin.created_at": if e.complexity.Checkin.CreatedAt == nil { break } return e.complexity.Checkin.CreatedAt(childComplexity), true - case "Checkin.Failing": + case "Checkin.failing": if e.complexity.Checkin.Failing == nil { break } return e.complexity.Checkin.Failing(childComplexity), true - case "Checkin.Failures": + case "Checkin.failures": if e.complexity.Checkin.Failures == nil { break } return e.complexity.Checkin.Failures(childComplexity), true - case "Checkin.GracePeriod": + case "Checkin.grace": if e.complexity.Checkin.GracePeriod == nil { break } return e.complexity.Checkin.GracePeriod(childComplexity), true - case "Checkin.Hits": + case "Checkin.hits": if e.complexity.Checkin.Hits == nil { break } return e.complexity.Checkin.Hits(childComplexity), true - case "Checkin.Id": + case "Checkin.id": if e.complexity.Checkin.Id == nil { break } return e.complexity.Checkin.Id(childComplexity), true - case "Checkin.Interval": + case "Checkin.interval": if e.complexity.Checkin.Interval == nil { break } return e.complexity.Checkin.Interval(childComplexity), true - case "Checkin.LastHit": + case "Checkin.last_hit": if e.complexity.Checkin.LastHit == nil { break } return e.complexity.Checkin.LastHit(childComplexity), true - case "Checkin.Name": + case "Checkin.name": if e.complexity.Checkin.Name == nil { break } return e.complexity.Checkin.Name(childComplexity), true - case "Checkin.Service": + case "Checkin.service": if e.complexity.Checkin.Service == nil { break } return e.complexity.Checkin.Service(childComplexity), true - case "Checkin.UpdatedAt": + case "Checkin.updated_at": if e.complexity.Checkin.UpdatedAt == nil { break } return e.complexity.Checkin.UpdatedAt(childComplexity), true - case "CheckinHit.CreatedAt": + case "CheckinHit.created_at": if e.complexity.CheckinHit.CreatedAt == nil { break } return e.complexity.CheckinHit.CreatedAt(childComplexity), true - case "CheckinHit.From": + case "CheckinHit.from": if e.complexity.CheckinHit.From == nil { break } return e.complexity.CheckinHit.From(childComplexity), true - case "CheckinHit.Id": + case "CheckinHit.id": if e.complexity.CheckinHit.Id == nil { break } return e.complexity.CheckinHit.Id(childComplexity), true - case "Core.CreatedAt": + case "Core.created_at": if e.complexity.Core.CreatedAt == nil { break } return e.complexity.Core.CreatedAt(childComplexity), true - case "Core.Description": + case "Core.description": if e.complexity.Core.Description == nil { break } return e.complexity.Core.Description(childComplexity), true - case "Core.Domain": + case "Core.domain": if e.complexity.Core.Domain == nil { break } return e.complexity.Core.Domain(childComplexity), true - case "Core.Footer": + case "Core.footer": if e.complexity.Core.Footer == nil { break } return e.complexity.Core.Footer(childComplexity), true - case "Core.Name": + case "Core.name": if e.complexity.Core.Name == nil { break } return e.complexity.Core.Name(childComplexity), true - case "Core.Started": + case "Core.started_on": if e.complexity.Core.Started == nil { break } return e.complexity.Core.Started(childComplexity), true - case "Core.Timezone": + case "Core.timezone": if e.complexity.Core.Timezone == nil { break } return e.complexity.Core.Timezone(childComplexity), true - case "Core.UpdatedAt": + case "Core.updated_at": if e.complexity.Core.UpdatedAt == nil { break } return e.complexity.Core.UpdatedAt(childComplexity), true - case "Core.UsingCdn": + case "Core.using_cdn": if e.complexity.Core.UsingCdn == nil { break } return e.complexity.Core.UsingCdn(childComplexity), true - case "Core.Version": + case "Core.version": if e.complexity.Core.Version == nil { break } return e.complexity.Core.Version(childComplexity), true - case "Failure.CreatedAt": + case "Failure.created_at": if e.complexity.Failure.CreatedAt == nil { break } return e.complexity.Failure.CreatedAt(childComplexity), true - case "Failure.ErrorCode": + case "Failure.error_code": if e.complexity.Failure.ErrorCode == nil { break } return e.complexity.Failure.ErrorCode(childComplexity), true - case "Failure.Id": + case "Failure.id": if e.complexity.Failure.Id == nil { break } return e.complexity.Failure.Id(childComplexity), true - case "Failure.Issue": + case "Failure.issue": if e.complexity.Failure.Issue == nil { break } return e.complexity.Failure.Issue(childComplexity), true - case "Failure.Method": + case "Failure.method": if e.complexity.Failure.Method == nil { break } return e.complexity.Failure.Method(childComplexity), true - case "Failure.MethodId": + case "Failure.method_id": if e.complexity.Failure.MethodId == nil { break } return e.complexity.Failure.MethodId(childComplexity), true - case "Failure.PingTime": + case "Failure.ping": if e.complexity.Failure.PingTime == nil { break } return e.complexity.Failure.PingTime(childComplexity), true - case "Group.CreatedAt": + case "Group.created_at": if e.complexity.Group.CreatedAt == nil { break } return e.complexity.Group.CreatedAt(childComplexity), true - case "Group.Id": + case "Group.id": if e.complexity.Group.Id == nil { break } return e.complexity.Group.Id(childComplexity), true - case "Group.Name": + case "Group.name": if e.complexity.Group.Name == nil { break } return e.complexity.Group.Name(childComplexity), true - case "Group.Order": + case "Group.order_id": if e.complexity.Group.Order == nil { break } return e.complexity.Group.Order(childComplexity), true - case "Group.Public": + case "Group.public": if e.complexity.Group.Public == nil { break } return e.complexity.Group.Public(childComplexity), true - case "Group.UpdatedAt": + case "Group.updated_at": if e.complexity.Group.UpdatedAt == nil { break } return e.complexity.Group.UpdatedAt(childComplexity), true - case "Message.CreatedAt": + case "Message.created_at": if e.complexity.Message.CreatedAt == nil { break } return e.complexity.Message.CreatedAt(childComplexity), true - case "Message.Description": + case "Message.description": if e.complexity.Message.Description == nil { break } return e.complexity.Message.Description(childComplexity), true - case "Message.EndOn": + case "Message.end_on": if e.complexity.Message.EndOn == nil { break } return e.complexity.Message.EndOn(childComplexity), true - case "Message.Id": + case "Message.id": if e.complexity.Message.Id == nil { break } return e.complexity.Message.Id(childComplexity), true - case "Message.NotifyBefore": + case "Message.notify_before": if e.complexity.Message.NotifyBefore == nil { break } return e.complexity.Message.NotifyBefore(childComplexity), true - case "Message.NotifyBeforeScale": + case "Message.notify_before_scale": if e.complexity.Message.NotifyBeforeScale == nil { break } return e.complexity.Message.NotifyBeforeScale(childComplexity), true - case "Message.NotifyMethod": + case "Message.notify_method": if e.complexity.Message.NotifyMethod == nil { break } return e.complexity.Message.NotifyMethod(childComplexity), true - case "Message.NotifyUsers": + case "Message.notify_users": if e.complexity.Message.NotifyUsers == nil { break } return e.complexity.Message.NotifyUsers(childComplexity), true - case "Message.StartOn": + case "Message.start_on": if e.complexity.Message.StartOn == nil { break } return e.complexity.Message.StartOn(childComplexity), true - case "Message.Title": + case "Message.title": if e.complexity.Message.Title == nil { break } return e.complexity.Message.Title(childComplexity), true - case "Message.UpdatedAt": + case "Message.updated_at": if e.complexity.Message.UpdatedAt == nil { break } return e.complexity.Message.UpdatedAt(childComplexity), true - case "Query.Checkin": + case "Query.checkin": if e.complexity.Query.Checkin == nil { break } @@ -592,21 +593,21 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Checkin(childComplexity, args["id"].(int64)), true - case "Query.Checkins": + case "Query.checkins": if e.complexity.Query.Checkins == nil { break } return e.complexity.Query.Checkins(childComplexity), true - case "Query.Core": + case "Query.core": if e.complexity.Query.Core == nil { break } return e.complexity.Query.Core(childComplexity), true - case "Query.Group": + case "Query.group": if e.complexity.Query.Group == nil { break } @@ -618,14 +619,14 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Group(childComplexity, args["id"].(int64)), true - case "Query.Groups": + case "Query.groups": if e.complexity.Query.Groups == nil { break } return e.complexity.Query.Groups(childComplexity), true - case "Query.Message": + case "Query.message": if e.complexity.Query.Message == nil { break } @@ -637,14 +638,14 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Message(childComplexity, args["id"].(int64)), true - case "Query.Messages": + case "Query.messages": if e.complexity.Query.Messages == nil { break } return e.complexity.Query.Messages(childComplexity), true - case "Query.Service": + case "Query.service": if e.complexity.Query.Service == nil { break } @@ -656,14 +657,14 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Service(childComplexity, args["id"].(int64)), true - case "Query.Services": + case "Query.services": if e.complexity.Query.Services == nil { break } return e.complexity.Query.Services(childComplexity), true - case "Query.User": + case "Query.user": if e.complexity.Query.User == nil { break } @@ -675,252 +676,252 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.User(childComplexity, args["id"].(int64)), true - case "Query.Users": + case "Query.users": if e.complexity.Query.Users == nil { break } return e.complexity.Query.Users(childComplexity), true - case "Service.AllowNotifications": + case "Service.allow_notifications": if e.complexity.Service.AllowNotifications == nil { break } return e.complexity.Service.AllowNotifications(childComplexity), true - case "Service.AvgResponse": + case "Service.avg_response": if e.complexity.Service.AvgResponse == nil { break } return e.complexity.Service.AvgResponse(childComplexity), true - case "Service.CreatedAt": + case "Service.created_at": if e.complexity.Service.CreatedAt == nil { break } return e.complexity.Service.CreatedAt(childComplexity), true - case "Service.Domain": + case "Service.domain": if e.complexity.Service.Domain == nil { break } return e.complexity.Service.Domain(childComplexity), true - case "Service.Expected": + case "Service.expected": if e.complexity.Service.Expected == nil { break } return e.complexity.Service.Expected(childComplexity), true - case "Service.ExpectedStatus": + case "Service.expected_status": if e.complexity.Service.ExpectedStatus == nil { break } return e.complexity.Service.ExpectedStatus(childComplexity), true - case "Service.Failures": + case "Service.failures": if e.complexity.Service.Failures == nil { break } return e.complexity.Service.Failures(childComplexity), true - case "Service.Group": + case "Service.group": if e.complexity.Service.Group == nil { break } return e.complexity.Service.Group(childComplexity), true - case "Service.Headers": + case "Service.headers": if e.complexity.Service.Headers == nil { break } return e.complexity.Service.Headers(childComplexity), true - case "Service.Id": + case "Service.id": if e.complexity.Service.Id == nil { break } return e.complexity.Service.Id(childComplexity), true - case "Service.Interval": + case "Service.interval": if e.complexity.Service.Interval == nil { break } return e.complexity.Service.Interval(childComplexity), true - case "Service.LastOnline": + case "Service.last_success": if e.complexity.Service.LastOnline == nil { break } return e.complexity.Service.LastOnline(childComplexity), true - case "Service.LastStatusCode": + case "Service.status_code": if e.complexity.Service.LastStatusCode == nil { break } return e.complexity.Service.LastStatusCode(childComplexity), true - case "Service.Latency": + case "Service.latency": if e.complexity.Service.Latency == nil { break } return e.complexity.Service.Latency(childComplexity), true - case "Service.Method": + case "Service.method": if e.complexity.Service.Method == nil { break } return e.complexity.Service.Method(childComplexity), true - case "Service.Name": + case "Service.name": if e.complexity.Service.Name == nil { break } return e.complexity.Service.Name(childComplexity), true - case "Service.Online": + case "Service.online": if e.complexity.Service.Online == nil { break } return e.complexity.Service.Online(childComplexity), true - case "Service.Online24Hours": + case "Service.online_24_hours": if e.complexity.Service.Online24Hours == nil { break } return e.complexity.Service.Online24Hours(childComplexity), true - case "Service.Order": + case "Service.order_id": if e.complexity.Service.Order == nil { break } return e.complexity.Service.Order(childComplexity), true - case "Service.Permalink": + case "Service.permalink": if e.complexity.Service.Permalink == nil { break } return e.complexity.Service.Permalink(childComplexity), true - case "Service.PingTime": + case "Service.ping_time": if e.complexity.Service.PingTime == nil { break } return e.complexity.Service.PingTime(childComplexity), true - case "Service.Port": + case "Service.port": if e.complexity.Service.Port == nil { break } return e.complexity.Service.Port(childComplexity), true - case "Service.PostData": + case "Service.post_data": if e.complexity.Service.PostData == nil { break } return e.complexity.Service.PostData(childComplexity), true - case "Service.Public": + case "Service.public": if e.complexity.Service.Public == nil { break } return e.complexity.Service.Public(childComplexity), true - case "Service.Timeout": + case "Service.timeout": if e.complexity.Service.Timeout == nil { break } return e.complexity.Service.Timeout(childComplexity), true - case "Service.Type": + case "Service.type": if e.complexity.Service.Type == nil { break } return e.complexity.Service.Type(childComplexity), true - case "Service.UpdatedAt": + case "Service.updated_at": if e.complexity.Service.UpdatedAt == nil { break } return e.complexity.Service.UpdatedAt(childComplexity), true - case "User.Admin": + case "User.admin": if e.complexity.User.Admin == nil { break } return e.complexity.User.Admin(childComplexity), true - case "User.ApiKey": + case "User.api_key": if e.complexity.User.ApiKey == nil { break } return e.complexity.User.ApiKey(childComplexity), true - case "User.ApiSecret": + case "User.api_secret": if e.complexity.User.ApiSecret == nil { break } return e.complexity.User.ApiSecret(childComplexity), true - case "User.CreatedAt": + case "User.created_at": if e.complexity.User.CreatedAt == nil { break } return e.complexity.User.CreatedAt(childComplexity), true - case "User.Email": + case "User.email": if e.complexity.User.Email == nil { break } return e.complexity.User.Email(childComplexity), true - case "User.Id": + case "User.id": if e.complexity.User.Id == nil { break } return e.complexity.User.Id(childComplexity), true - case "User.UpdatedAt": + case "User.updated_at": if e.complexity.User.UpdatedAt == nil { break } return e.complexity.User.UpdatedAt(childComplexity), true - case "User.Username": + case "User.username": if e.complexity.User.Username == nil { break } @@ -961,21 +962,6 @@ type executionContext struct { *executableSchema } -func (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - res, err := ec.ResolverMiddleware(ctx, next) - if err != nil { - ec.Error(ctx, err) - return nil - } - return res -} - func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") @@ -1238,11 +1224,21 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // endregion ***************************** args.gotpl ***************************** +// region ************************** directives.gotpl ************************** + +// endregion ************************** directives.gotpl ************************** + // region **************************** field.gotpl ***************************** -func (ec *executionContext) _Checkin_id(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_id(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1251,10 +1247,14 @@ func (ec *executionContext) _Checkin_id(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1267,9 +1267,15 @@ func (ec *executionContext) _Checkin_id(ctx context.Context, field graphql.Colle return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_service(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_service(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1278,10 +1284,14 @@ func (ec *executionContext) _Checkin_service(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Checkin().Service(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1294,9 +1304,15 @@ func (ec *executionContext) _Checkin_service(ctx context.Context, field graphql. return ec.marshalNService2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐService(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_name(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_name(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1305,10 +1321,14 @@ func (ec *executionContext) _Checkin_name(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1321,9 +1341,15 @@ func (ec *executionContext) _Checkin_name(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_interval(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_interval(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1332,10 +1358,14 @@ func (ec *executionContext) _Checkin_interval(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Interval, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1348,9 +1378,15 @@ func (ec *executionContext) _Checkin_interval(ctx context.Context, field graphql return ec.marshalNInt2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_grace(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_grace(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1359,10 +1395,14 @@ func (ec *executionContext) _Checkin_grace(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.GracePeriod, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1375,9 +1415,15 @@ func (ec *executionContext) _Checkin_grace(ctx context.Context, field graphql.Co return ec.marshalNInt2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_api_key(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_api_key(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1386,10 +1432,14 @@ func (ec *executionContext) _Checkin_api_key(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.ApiKey, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1402,9 +1452,15 @@ func (ec *executionContext) _Checkin_api_key(ctx context.Context, field graphql. return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_failing(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_failing(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1413,10 +1469,14 @@ func (ec *executionContext) _Checkin_failing(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Failing, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1429,9 +1489,15 @@ func (ec *executionContext) _Checkin_failing(ctx context.Context, field graphql. return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_last_hit(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_last_hit(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1440,10 +1506,14 @@ func (ec *executionContext) _Checkin_last_hit(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.LastHit, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1456,9 +1526,15 @@ func (ec *executionContext) _Checkin_last_hit(ctx context.Context, field graphql return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_failures(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_failures(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1467,10 +1543,14 @@ func (ec *executionContext) _Checkin_failures(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Checkin().Failures(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -1480,9 +1560,15 @@ func (ec *executionContext) _Checkin_failures(ctx context.Context, field graphql return ec.marshalOFailure2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐFailure(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_hits(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_hits(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1491,10 +1577,14 @@ func (ec *executionContext) _Checkin_hits(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Hits, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -1504,9 +1594,15 @@ func (ec *executionContext) _Checkin_hits(ctx context.Context, field graphql.Col return ec.marshalOCheckinHit2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐCheckinHit(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1515,10 +1611,14 @@ func (ec *executionContext) _Checkin_created_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1531,9 +1631,15 @@ func (ec *executionContext) _Checkin_created_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Checkin_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) graphql.Marshaler { +func (ec *executionContext) _Checkin_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Checkin) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Checkin", Field: field, @@ -1542,10 +1648,14 @@ func (ec *executionContext) _Checkin_updated_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.UpdatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1558,9 +1668,15 @@ func (ec *executionContext) _Checkin_updated_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _CheckinHit_id(ctx context.Context, field graphql.CollectedField, obj *types.CheckinHit) graphql.Marshaler { +func (ec *executionContext) _CheckinHit_id(ctx context.Context, field graphql.CollectedField, obj *types.CheckinHit) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "CheckinHit", Field: field, @@ -1569,10 +1685,14 @@ func (ec *executionContext) _CheckinHit_id(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1585,9 +1705,15 @@ func (ec *executionContext) _CheckinHit_id(ctx context.Context, field graphql.Co return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _CheckinHit_from(ctx context.Context, field graphql.CollectedField, obj *types.CheckinHit) graphql.Marshaler { +func (ec *executionContext) _CheckinHit_from(ctx context.Context, field graphql.CollectedField, obj *types.CheckinHit) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "CheckinHit", Field: field, @@ -1596,10 +1722,14 @@ func (ec *executionContext) _CheckinHit_from(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.From, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1612,9 +1742,15 @@ func (ec *executionContext) _CheckinHit_from(ctx context.Context, field graphql. return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _CheckinHit_created_at(ctx context.Context, field graphql.CollectedField, obj *types.CheckinHit) graphql.Marshaler { +func (ec *executionContext) _CheckinHit_created_at(ctx context.Context, field graphql.CollectedField, obj *types.CheckinHit) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "CheckinHit", Field: field, @@ -1623,10 +1759,14 @@ func (ec *executionContext) _CheckinHit_created_at(ctx context.Context, field gr } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1639,9 +1779,15 @@ func (ec *executionContext) _CheckinHit_created_at(ctx context.Context, field gr return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Core_name(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_name(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1650,10 +1796,14 @@ func (ec *executionContext) _Core_name(ctx context.Context, field graphql.Collec } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1666,9 +1816,15 @@ func (ec *executionContext) _Core_name(ctx context.Context, field graphql.Collec return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Core_description(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_description(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1677,10 +1833,14 @@ func (ec *executionContext) _Core_description(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1693,9 +1853,15 @@ func (ec *executionContext) _Core_description(ctx context.Context, field graphql return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Core_footer(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_footer(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1704,10 +1870,14 @@ func (ec *executionContext) _Core_footer(ctx context.Context, field graphql.Coll } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Core().Footer(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1720,9 +1890,15 @@ func (ec *executionContext) _Core_footer(ctx context.Context, field graphql.Coll return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Core_domain(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_domain(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1731,10 +1907,14 @@ func (ec *executionContext) _Core_domain(ctx context.Context, field graphql.Coll } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Domain, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1747,9 +1927,15 @@ func (ec *executionContext) _Core_domain(ctx context.Context, field graphql.Coll return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Core_version(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_version(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1758,10 +1944,14 @@ func (ec *executionContext) _Core_version(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Version, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1774,9 +1964,15 @@ func (ec *executionContext) _Core_version(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Core_timezone(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_timezone(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1785,10 +1981,14 @@ func (ec *executionContext) _Core_timezone(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Core().Timezone(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1801,9 +2001,15 @@ func (ec *executionContext) _Core_timezone(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Core_using_cdn(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_using_cdn(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1812,10 +2018,14 @@ func (ec *executionContext) _Core_using_cdn(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Core().UsingCdn(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1828,9 +2038,15 @@ func (ec *executionContext) _Core_using_cdn(ctx context.Context, field graphql.C return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Core_started_on(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_started_on(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1839,10 +2055,14 @@ func (ec *executionContext) _Core_started_on(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Started, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1855,9 +2075,15 @@ func (ec *executionContext) _Core_started_on(ctx context.Context, field graphql. return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Core_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1866,10 +2092,14 @@ func (ec *executionContext) _Core_created_at(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1882,9 +2112,15 @@ func (ec *executionContext) _Core_created_at(ctx context.Context, field graphql. return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Core_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Core) graphql.Marshaler { +func (ec *executionContext) _Core_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Core) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Core", Field: field, @@ -1893,10 +2129,14 @@ func (ec *executionContext) _Core_updated_at(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.UpdatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1909,9 +2149,15 @@ func (ec *executionContext) _Core_updated_at(ctx context.Context, field graphql. return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_id(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_id(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -1920,10 +2166,14 @@ func (ec *executionContext) _Failure_id(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1936,9 +2186,15 @@ func (ec *executionContext) _Failure_id(ctx context.Context, field graphql.Colle return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_issue(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_issue(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -1947,10 +2203,14 @@ func (ec *executionContext) _Failure_issue(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Issue, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1963,9 +2223,15 @@ func (ec *executionContext) _Failure_issue(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_method(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_method(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -1974,10 +2240,14 @@ func (ec *executionContext) _Failure_method(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Method, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -1990,9 +2260,15 @@ func (ec *executionContext) _Failure_method(ctx context.Context, field graphql.C return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_method_id(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_method_id(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -2001,10 +2277,14 @@ func (ec *executionContext) _Failure_method_id(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.MethodId, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2017,9 +2297,15 @@ func (ec *executionContext) _Failure_method_id(ctx context.Context, field graphq return ec.marshalNInt2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_error_code(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_error_code(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -2028,10 +2314,14 @@ func (ec *executionContext) _Failure_error_code(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.ErrorCode, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2044,9 +2334,15 @@ func (ec *executionContext) _Failure_error_code(ctx context.Context, field graph return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_ping(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_ping(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -2055,10 +2351,14 @@ func (ec *executionContext) _Failure_ping(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.PingTime, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2071,9 +2371,15 @@ func (ec *executionContext) _Failure_ping(ctx context.Context, field graphql.Col return ec.marshalNFloat2float64(ctx, field.Selections, res) } -func (ec *executionContext) _Failure_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Failure) graphql.Marshaler { +func (ec *executionContext) _Failure_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Failure) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Failure", Field: field, @@ -2082,10 +2388,14 @@ func (ec *executionContext) _Failure_created_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2098,9 +2408,15 @@ func (ec *executionContext) _Failure_created_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Group_id(ctx context.Context, field graphql.CollectedField, obj *types.Group) graphql.Marshaler { +func (ec *executionContext) _Group_id(ctx context.Context, field graphql.CollectedField, obj *types.Group) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Group", Field: field, @@ -2109,10 +2425,14 @@ func (ec *executionContext) _Group_id(ctx context.Context, field graphql.Collect } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2125,9 +2445,15 @@ func (ec *executionContext) _Group_id(ctx context.Context, field graphql.Collect return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Group_name(ctx context.Context, field graphql.CollectedField, obj *types.Group) graphql.Marshaler { +func (ec *executionContext) _Group_name(ctx context.Context, field graphql.CollectedField, obj *types.Group) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Group", Field: field, @@ -2136,10 +2462,14 @@ func (ec *executionContext) _Group_name(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2152,9 +2482,15 @@ func (ec *executionContext) _Group_name(ctx context.Context, field graphql.Colle return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Group_public(ctx context.Context, field graphql.CollectedField, obj *types.Group) graphql.Marshaler { +func (ec *executionContext) _Group_public(ctx context.Context, field graphql.CollectedField, obj *types.Group) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Group", Field: field, @@ -2163,10 +2499,14 @@ func (ec *executionContext) _Group_public(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Group().Public(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2179,9 +2519,15 @@ func (ec *executionContext) _Group_public(ctx context.Context, field graphql.Col return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Group_order_id(ctx context.Context, field graphql.CollectedField, obj *types.Group) graphql.Marshaler { +func (ec *executionContext) _Group_order_id(ctx context.Context, field graphql.CollectedField, obj *types.Group) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Group", Field: field, @@ -2190,10 +2536,14 @@ func (ec *executionContext) _Group_order_id(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Order, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2206,9 +2556,15 @@ func (ec *executionContext) _Group_order_id(ctx context.Context, field graphql.C return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Group_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Group) graphql.Marshaler { +func (ec *executionContext) _Group_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Group) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Group", Field: field, @@ -2217,10 +2573,14 @@ func (ec *executionContext) _Group_created_at(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2233,9 +2593,15 @@ func (ec *executionContext) _Group_created_at(ctx context.Context, field graphql return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Group_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Group) graphql.Marshaler { +func (ec *executionContext) _Group_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Group) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Group", Field: field, @@ -2244,10 +2610,14 @@ func (ec *executionContext) _Group_updated_at(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.UpdatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2260,9 +2630,15 @@ func (ec *executionContext) _Group_updated_at(ctx context.Context, field graphql return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Message_id(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_id(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2271,10 +2647,14 @@ func (ec *executionContext) _Message_id(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2287,9 +2667,15 @@ func (ec *executionContext) _Message_id(ctx context.Context, field graphql.Colle return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Message_title(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_title(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2298,10 +2684,14 @@ func (ec *executionContext) _Message_title(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Title, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2314,9 +2704,15 @@ func (ec *executionContext) _Message_title(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Message_description(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_description(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2325,10 +2721,14 @@ func (ec *executionContext) _Message_description(ctx context.Context, field grap } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2341,9 +2741,15 @@ func (ec *executionContext) _Message_description(ctx context.Context, field grap return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Message_start_on(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_start_on(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2352,10 +2758,14 @@ func (ec *executionContext) _Message_start_on(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.StartOn, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2368,9 +2778,15 @@ func (ec *executionContext) _Message_start_on(ctx context.Context, field graphql return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Message_end_on(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_end_on(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2379,10 +2795,14 @@ func (ec *executionContext) _Message_end_on(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.EndOn, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2395,9 +2815,15 @@ func (ec *executionContext) _Message_end_on(ctx context.Context, field graphql.C return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Message_notify_users(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_notify_users(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2406,10 +2832,14 @@ func (ec *executionContext) _Message_notify_users(ctx context.Context, field gra } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Message().NotifyUsers(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2422,9 +2852,15 @@ func (ec *executionContext) _Message_notify_users(ctx context.Context, field gra return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Message_notify_method(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_notify_method(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2433,10 +2869,14 @@ func (ec *executionContext) _Message_notify_method(ctx context.Context, field gr } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Message().NotifyMethod(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2449,9 +2889,15 @@ func (ec *executionContext) _Message_notify_method(ctx context.Context, field gr return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Message_notify_before(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_notify_before(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2460,10 +2906,14 @@ func (ec *executionContext) _Message_notify_before(ctx context.Context, field gr } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Message().NotifyBefore(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2476,9 +2926,15 @@ func (ec *executionContext) _Message_notify_before(ctx context.Context, field gr return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Message_notify_before_scale(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_notify_before_scale(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2487,10 +2943,14 @@ func (ec *executionContext) _Message_notify_before_scale(ctx context.Context, fi } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.NotifyBeforeScale, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2503,9 +2963,15 @@ func (ec *executionContext) _Message_notify_before_scale(ctx context.Context, fi return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Message_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2514,10 +2980,14 @@ func (ec *executionContext) _Message_created_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2530,9 +3000,15 @@ func (ec *executionContext) _Message_created_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Message_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Message) graphql.Marshaler { +func (ec *executionContext) _Message_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Message) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Message", Field: field, @@ -2541,10 +3017,14 @@ func (ec *executionContext) _Message_updated_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.UpdatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2557,9 +3037,15 @@ func (ec *executionContext) _Message_updated_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Query_core(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_core(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2568,10 +3054,14 @@ func (ec *executionContext) _Query_core(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Core(rctx) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2581,9 +3071,15 @@ func (ec *executionContext) _Query_core(ctx context.Context, field graphql.Colle return ec.marshalOCore2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐCore(ctx, field.Selections, res) } -func (ec *executionContext) _Query_service(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_service(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2599,10 +3095,14 @@ func (ec *executionContext) _Query_service(ctx context.Context, field graphql.Co } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Service(rctx, args["id"].(int64)) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2612,9 +3112,15 @@ func (ec *executionContext) _Query_service(ctx context.Context, field graphql.Co return ec.marshalOService2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐService(ctx, field.Selections, res) } -func (ec *executionContext) _Query_services(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_services(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2623,10 +3129,14 @@ func (ec *executionContext) _Query_services(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Services(rctx) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2639,9 +3149,15 @@ func (ec *executionContext) _Query_services(ctx context.Context, field graphql.C return ec.marshalNService2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐService(ctx, field.Selections, res) } -func (ec *executionContext) _Query_group(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_group(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2657,10 +3173,14 @@ func (ec *executionContext) _Query_group(ctx context.Context, field graphql.Coll } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Group(rctx, args["id"].(int64)) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2670,9 +3190,15 @@ func (ec *executionContext) _Query_group(ctx context.Context, field graphql.Coll return ec.marshalOGroup2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐGroup(ctx, field.Selections, res) } -func (ec *executionContext) _Query_groups(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_groups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2681,10 +3207,14 @@ func (ec *executionContext) _Query_groups(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Groups(rctx) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2697,9 +3227,15 @@ func (ec *executionContext) _Query_groups(ctx context.Context, field graphql.Col return ec.marshalNGroup2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐGroup(ctx, field.Selections, res) } -func (ec *executionContext) _Query_user(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2715,10 +3251,14 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().User(rctx, args["id"].(int64)) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2728,9 +3268,15 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle return ec.marshalOUser2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐUser(ctx, field.Selections, res) } -func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2739,10 +3285,14 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Users(rctx) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2755,9 +3305,15 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll return ec.marshalNUser2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐUser(ctx, field.Selections, res) } -func (ec *executionContext) _Query_checkin(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_checkin(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2773,10 +3329,14 @@ func (ec *executionContext) _Query_checkin(ctx context.Context, field graphql.Co } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Checkin(rctx, args["id"].(int64)) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2786,9 +3346,15 @@ func (ec *executionContext) _Query_checkin(ctx context.Context, field graphql.Co return ec.marshalOCheckin2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐCheckin(ctx, field.Selections, res) } -func (ec *executionContext) _Query_checkins(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_checkins(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2797,10 +3363,14 @@ func (ec *executionContext) _Query_checkins(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Checkins(rctx) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2813,9 +3383,15 @@ func (ec *executionContext) _Query_checkins(ctx context.Context, field graphql.C return ec.marshalNCheckin2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐCheckin(ctx, field.Selections, res) } -func (ec *executionContext) _Query_message(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_message(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2831,10 +3407,14 @@ func (ec *executionContext) _Query_message(ctx context.Context, field graphql.Co } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Message(rctx, args["id"].(int64)) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2844,9 +3424,15 @@ func (ec *executionContext) _Query_message(ctx context.Context, field graphql.Co return ec.marshalOMessage2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐMessage(ctx, field.Selections, res) } -func (ec *executionContext) _Query_messages(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query_messages(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2855,10 +3441,14 @@ func (ec *executionContext) _Query_messages(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Query().Messages(rctx) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2871,9 +3461,15 @@ func (ec *executionContext) _Query_messages(ctx context.Context, field graphql.C return ec.marshalNMessage2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐMessage(ctx, field.Selections, res) } -func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2889,10 +3485,14 @@ func (ec *executionContext) _Query___type(ctx context.Context, field graphql.Col } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.introspectType(args["name"].(string)) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2902,9 +3502,15 @@ func (ec *executionContext) _Query___type(ctx context.Context, field graphql.Col return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { +func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Query", Field: field, @@ -2913,10 +3519,14 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.introspectSchema() }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -2926,9 +3536,15 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res) } -func (ec *executionContext) _Service_id(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_id(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -2937,10 +3553,14 @@ func (ec *executionContext) _Service_id(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2953,9 +3573,15 @@ func (ec *executionContext) _Service_id(ctx context.Context, field graphql.Colle return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _Service_name(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_name(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -2964,10 +3590,14 @@ func (ec *executionContext) _Service_name(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -2980,9 +3610,15 @@ func (ec *executionContext) _Service_name(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_domain(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_domain(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -2991,10 +3627,14 @@ func (ec *executionContext) _Service_domain(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Domain, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3007,9 +3647,15 @@ func (ec *executionContext) _Service_domain(ctx context.Context, field graphql.C return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_expected(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_expected(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3018,10 +3664,14 @@ func (ec *executionContext) _Service_expected(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Expected(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3034,9 +3684,15 @@ func (ec *executionContext) _Service_expected(ctx context.Context, field graphql return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_expected_status(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_expected_status(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3045,10 +3701,14 @@ func (ec *executionContext) _Service_expected_status(ctx context.Context, field } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.ExpectedStatus, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3061,9 +3721,15 @@ func (ec *executionContext) _Service_expected_status(ctx context.Context, field return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Service_interval(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_interval(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3072,10 +3738,14 @@ func (ec *executionContext) _Service_interval(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Interval, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3088,9 +3758,15 @@ func (ec *executionContext) _Service_interval(ctx context.Context, field graphql return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Service_type(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_type(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3099,10 +3775,14 @@ func (ec *executionContext) _Service_type(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Type, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3115,9 +3795,15 @@ func (ec *executionContext) _Service_type(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_method(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_method(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3126,10 +3812,14 @@ func (ec *executionContext) _Service_method(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Method, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3142,9 +3832,15 @@ func (ec *executionContext) _Service_method(ctx context.Context, field graphql.C return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_post_data(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_post_data(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3153,10 +3849,14 @@ func (ec *executionContext) _Service_post_data(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().PostData(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3169,9 +3869,15 @@ func (ec *executionContext) _Service_post_data(ctx context.Context, field graphq return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_port(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_port(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3180,10 +3886,14 @@ func (ec *executionContext) _Service_port(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Port, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3196,9 +3906,15 @@ func (ec *executionContext) _Service_port(ctx context.Context, field graphql.Col return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Service_timeout(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_timeout(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3207,10 +3923,14 @@ func (ec *executionContext) _Service_timeout(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Timeout, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3223,9 +3943,15 @@ func (ec *executionContext) _Service_timeout(ctx context.Context, field graphql. return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Service_order_id(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_order_id(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3234,10 +3960,14 @@ func (ec *executionContext) _Service_order_id(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Order, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3250,9 +3980,15 @@ func (ec *executionContext) _Service_order_id(ctx context.Context, field graphql return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Service_allow_notifications(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_allow_notifications(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3261,10 +3997,14 @@ func (ec *executionContext) _Service_allow_notifications(ctx context.Context, fi } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().AllowNotifications(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3277,9 +4017,15 @@ func (ec *executionContext) _Service_allow_notifications(ctx context.Context, fi return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Service_public(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_public(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3288,10 +4034,14 @@ func (ec *executionContext) _Service_public(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Public(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3304,9 +4054,15 @@ func (ec *executionContext) _Service_public(ctx context.Context, field graphql.C return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Service_group(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_group(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3315,10 +4071,14 @@ func (ec *executionContext) _Service_group(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Group(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3331,9 +4091,15 @@ func (ec *executionContext) _Service_group(ctx context.Context, field graphql.Co return ec.marshalNGroup2ᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐGroup(ctx, field.Selections, res) } -func (ec *executionContext) _Service_headers(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_headers(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3342,10 +4108,14 @@ func (ec *executionContext) _Service_headers(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Headers(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3358,9 +4128,15 @@ func (ec *executionContext) _Service_headers(ctx context.Context, field graphql. return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_permalink(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_permalink(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3369,10 +4145,14 @@ func (ec *executionContext) _Service_permalink(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Permalink(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3385,9 +4165,15 @@ func (ec *executionContext) _Service_permalink(ctx context.Context, field graphq return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_online(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_online(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3396,10 +4182,14 @@ func (ec *executionContext) _Service_online(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Online, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3412,9 +4202,15 @@ func (ec *executionContext) _Service_online(ctx context.Context, field graphql.C return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Service_latency(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_latency(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3423,10 +4219,14 @@ func (ec *executionContext) _Service_latency(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Latency, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3439,9 +4239,15 @@ func (ec *executionContext) _Service_latency(ctx context.Context, field graphql. return ec.marshalNFloat2float64(ctx, field.Selections, res) } -func (ec *executionContext) _Service_ping_time(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_ping_time(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3450,10 +4256,14 @@ func (ec *executionContext) _Service_ping_time(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.PingTime, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3466,9 +4276,15 @@ func (ec *executionContext) _Service_ping_time(ctx context.Context, field graphq return ec.marshalNFloat2float64(ctx, field.Selections, res) } -func (ec *executionContext) _Service_online_24_hours(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_online_24_hours(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3477,10 +4293,14 @@ func (ec *executionContext) _Service_online_24_hours(ctx context.Context, field } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Online24Hours(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3493,9 +4313,15 @@ func (ec *executionContext) _Service_online_24_hours(ctx context.Context, field return ec.marshalNFloat2float64(ctx, field.Selections, res) } -func (ec *executionContext) _Service_avg_response(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_avg_response(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3504,10 +4330,14 @@ func (ec *executionContext) _Service_avg_response(ctx context.Context, field gra } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.AvgResponse, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3520,9 +4350,15 @@ func (ec *executionContext) _Service_avg_response(ctx context.Context, field gra return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Service_status_code(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_status_code(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3531,10 +4367,14 @@ func (ec *executionContext) _Service_status_code(ctx context.Context, field grap } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.LastStatusCode, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3547,9 +4387,15 @@ func (ec *executionContext) _Service_status_code(ctx context.Context, field grap return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _Service_last_success(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_last_success(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3558,10 +4404,14 @@ func (ec *executionContext) _Service_last_success(ctx context.Context, field gra } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.LastOnline, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3574,9 +4424,15 @@ func (ec *executionContext) _Service_last_success(ctx context.Context, field gra return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Service_failures(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_failures(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3585,10 +4441,14 @@ func (ec *executionContext) _Service_failures(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.Service().Failures(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -3598,9 +4458,15 @@ func (ec *executionContext) _Service_failures(ctx context.Context, field graphql return ec.marshalOFailure2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐFailure(ctx, field.Selections, res) } -func (ec *executionContext) _Service_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_created_at(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3609,10 +4475,14 @@ func (ec *executionContext) _Service_created_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3625,9 +4495,15 @@ func (ec *executionContext) _Service_created_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Service_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Service) graphql.Marshaler { +func (ec *executionContext) _Service_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.Service) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "Service", Field: field, @@ -3636,10 +4512,14 @@ func (ec *executionContext) _Service_updated_at(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.UpdatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3652,9 +4532,15 @@ func (ec *executionContext) _Service_updated_at(ctx context.Context, field graph return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3663,10 +4549,14 @@ func (ec *executionContext) _User_id(ctx context.Context, field graphql.Collecte } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Id, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3679,9 +4569,15 @@ func (ec *executionContext) _User_id(ctx context.Context, field graphql.Collecte return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3690,10 +4586,14 @@ func (ec *executionContext) _User_username(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Username, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3706,9 +4606,15 @@ func (ec *executionContext) _User_username(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _User_email(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_email(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3717,10 +4623,14 @@ func (ec *executionContext) _User_email(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Email, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3733,9 +4643,15 @@ func (ec *executionContext) _User_email(ctx context.Context, field graphql.Colle return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _User_api_key(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_api_key(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3744,10 +4660,14 @@ func (ec *executionContext) _User_api_key(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.ApiKey, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3760,9 +4680,15 @@ func (ec *executionContext) _User_api_key(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _User_api_secret(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_api_secret(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3771,10 +4697,14 @@ func (ec *executionContext) _User_api_secret(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.ApiSecret, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3787,9 +4717,15 @@ func (ec *executionContext) _User_api_secret(ctx context.Context, field graphql. return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _User_admin(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_admin(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3798,10 +4734,14 @@ func (ec *executionContext) _User_admin(ctx context.Context, field graphql.Colle } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return ec.resolvers.User().Admin(rctx, obj) }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3814,9 +4754,15 @@ func (ec *executionContext) _User_admin(ctx context.Context, field graphql.Colle return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _User_created_at(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_created_at(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3825,10 +4771,14 @@ func (ec *executionContext) _User_created_at(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.CreatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3841,9 +4791,15 @@ func (ec *executionContext) _User_created_at(ctx context.Context, field graphql. return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _User_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.User) graphql.Marshaler { +func (ec *executionContext) _User_updated_at(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "User", Field: field, @@ -3852,10 +4808,14 @@ func (ec *executionContext) _User_updated_at(ctx context.Context, field graphql. } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.UpdatedAt, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3868,9 +4828,15 @@ func (ec *executionContext) _User_updated_at(ctx context.Context, field graphql. return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) graphql.Marshaler { +func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Directive", Field: field, @@ -3879,10 +4845,14 @@ func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3895,9 +4865,15 @@ func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) graphql.Marshaler { +func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Directive", Field: field, @@ -3906,10 +4882,14 @@ func (ec *executionContext) ___Directive_description(ctx context.Context, field } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -3919,9 +4899,15 @@ func (ec *executionContext) ___Directive_description(ctx context.Context, field return ec.marshalOString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) graphql.Marshaler { +func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Directive", Field: field, @@ -3930,10 +4916,14 @@ func (ec *executionContext) ___Directive_locations(ctx context.Context, field gr } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Locations, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3946,9 +4936,15 @@ func (ec *executionContext) ___Directive_locations(ctx context.Context, field gr return ec.marshalN__DirectiveLocation2ᚕstring(ctx, field.Selections, res) } -func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) graphql.Marshaler { +func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Directive", Field: field, @@ -3957,10 +4953,14 @@ func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Args, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -3973,9 +4973,15 @@ func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) graphql.Marshaler { +func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__EnumValue", Field: field, @@ -3984,10 +4990,14 @@ func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4000,9 +5010,15 @@ func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) graphql.Marshaler { +func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__EnumValue", Field: field, @@ -4011,10 +5027,14 @@ func (ec *executionContext) ___EnumValue_description(ctx context.Context, field } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4024,9 +5044,15 @@ func (ec *executionContext) ___EnumValue_description(ctx context.Context, field return ec.marshalOString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) graphql.Marshaler { +func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__EnumValue", Field: field, @@ -4035,10 +5061,14 @@ func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.IsDeprecated(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4051,9 +5081,15 @@ func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) graphql.Marshaler { +func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__EnumValue", Field: field, @@ -4062,10 +5098,14 @@ func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.DeprecationReason(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4075,9 +5115,15 @@ func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Field", Field: field, @@ -4086,10 +5132,14 @@ func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4102,9 +5152,15 @@ func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Field", Field: field, @@ -4113,10 +5169,14 @@ func (ec *executionContext) ___Field_description(ctx context.Context, field grap } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4126,9 +5186,15 @@ func (ec *executionContext) ___Field_description(ctx context.Context, field grap return ec.marshalOString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Field", Field: field, @@ -4137,10 +5203,14 @@ func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Args, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4153,9 +5223,15 @@ func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.Col return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Field", Field: field, @@ -4164,10 +5240,14 @@ func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.Col } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Type, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4180,9 +5260,15 @@ func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.Col return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Field", Field: field, @@ -4191,10 +5277,14 @@ func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field gra } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.IsDeprecated(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4207,9 +5297,15 @@ func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field gra return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) graphql.Marshaler { +func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Field", Field: field, @@ -4218,10 +5314,14 @@ func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, fiel } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.DeprecationReason(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4231,9 +5331,15 @@ func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, fiel return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) graphql.Marshaler { +func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__InputValue", Field: field, @@ -4242,10 +5348,14 @@ func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4258,9 +5368,15 @@ func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphq return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___InputValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) graphql.Marshaler { +func (ec *executionContext) ___InputValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__InputValue", Field: field, @@ -4269,10 +5385,14 @@ func (ec *executionContext) ___InputValue_description(ctx context.Context, field } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4282,9 +5402,15 @@ func (ec *executionContext) ___InputValue_description(ctx context.Context, field return ec.marshalOString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) graphql.Marshaler { +func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__InputValue", Field: field, @@ -4293,10 +5419,14 @@ func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Type, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4309,9 +5439,15 @@ func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphq return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) graphql.Marshaler { +func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__InputValue", Field: field, @@ -4320,10 +5456,14 @@ func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, fiel } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.DefaultValue, nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4333,9 +5473,15 @@ func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, fiel return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) graphql.Marshaler { +func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Schema", Field: field, @@ -4344,10 +5490,14 @@ func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.C } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Types(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4360,9 +5510,15 @@ func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.C return ec.marshalN__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) graphql.Marshaler { +func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Schema", Field: field, @@ -4371,10 +5527,14 @@ func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.QueryType(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4387,9 +5547,15 @@ func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graph return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) graphql.Marshaler { +func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Schema", Field: field, @@ -4398,10 +5564,14 @@ func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field gr } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.MutationType(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4411,9 +5581,15 @@ func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field gr return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) graphql.Marshaler { +func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Schema", Field: field, @@ -4422,10 +5598,14 @@ func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, fiel } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.SubscriptionType(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4435,9 +5615,15 @@ func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, fiel return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Schema_directives(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) graphql.Marshaler { +func (ec *executionContext) ___Schema_directives(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Schema", Field: field, @@ -4446,10 +5632,14 @@ func (ec *executionContext) ___Schema_directives(ctx context.Context, field grap } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Directives(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4462,9 +5652,15 @@ func (ec *executionContext) ___Schema_directives(ctx context.Context, field grap return ec.marshalN__Directive2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4473,10 +5669,14 @@ func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.Coll } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Kind(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { if !ec.HasError(rctx) { ec.Errorf(ctx, "must not be null") @@ -4489,9 +5689,15 @@ func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.Coll return ec.marshalN__TypeKind2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4500,10 +5706,14 @@ func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.Coll } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Name(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4513,9 +5723,15 @@ func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.Coll return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4524,10 +5740,14 @@ func (ec *executionContext) ___Type_description(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Description(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4537,9 +5757,15 @@ func (ec *executionContext) ___Type_description(ctx context.Context, field graph return ec.marshalOString2string(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4555,10 +5781,14 @@ func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.Co } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Fields(args["includeDeprecated"].(bool)), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4568,9 +5798,15 @@ func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.Co return ec.marshalO__Field2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐField(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4579,10 +5815,14 @@ func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphq } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.Interfaces(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4592,9 +5832,15 @@ func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphq return ec.marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4603,10 +5849,14 @@ func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field gra } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.PossibleTypes(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4616,9 +5866,15 @@ func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field gra return ec.marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4634,10 +5890,14 @@ func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphq } rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.EnumValues(args["includeDeprecated"].(bool)), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4647,9 +5907,15 @@ func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphq return ec.marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValue(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4658,10 +5924,14 @@ func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graph } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.InputFields(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4671,9 +5941,15 @@ func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graph return ec.marshalO__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, field.Selections, res) } -func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) graphql.Marshaler { +func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func() { ec.Tracer.EndFieldExecution(ctx) }() + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() rctx := &graphql.ResolverContext{ Object: "__Type", Field: field, @@ -4682,10 +5958,14 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children return obj.OfType(), nil }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } if resTmp == nil { return graphql.Null } @@ -4710,10 +5990,10 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co var checkinImplementors = []string{"Checkin"} func (ec *executionContext) _Checkin(ctx context.Context, sel ast.SelectionSet, obj *types.Checkin) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, checkinImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, checkinImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -4721,7 +6001,7 @@ func (ec *executionContext) _Checkin(ctx context.Context, sel ast.SelectionSet, case "id": out.Values[i] = ec._Checkin_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "service": field := field @@ -4733,39 +6013,39 @@ func (ec *executionContext) _Checkin(ctx context.Context, sel ast.SelectionSet, }() res = ec._Checkin_service(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "name": out.Values[i] = ec._Checkin_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "interval": out.Values[i] = ec._Checkin_interval(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "grace": out.Values[i] = ec._Checkin_grace(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "api_key": out.Values[i] = ec._Checkin_api_key(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "failing": out.Values[i] = ec._Checkin_failing(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "last_hit": out.Values[i] = ec._Checkin_last_hit(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "failures": field := field @@ -4783,19 +6063,19 @@ func (ec *executionContext) _Checkin(ctx context.Context, sel ast.SelectionSet, case "created_at": out.Values[i] = ec._Checkin_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "updated_at": out.Values[i] = ec._Checkin_updated_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -4804,10 +6084,10 @@ func (ec *executionContext) _Checkin(ctx context.Context, sel ast.SelectionSet, var checkinHitImplementors = []string{"CheckinHit"} func (ec *executionContext) _CheckinHit(ctx context.Context, sel ast.SelectionSet, obj *types.CheckinHit) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, checkinHitImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, checkinHitImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -4815,24 +6095,24 @@ func (ec *executionContext) _CheckinHit(ctx context.Context, sel ast.SelectionSe case "id": out.Values[i] = ec._CheckinHit_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "from": out.Values[i] = ec._CheckinHit_from(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "created_at": out.Values[i] = ec._CheckinHit_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -4841,10 +6121,10 @@ func (ec *executionContext) _CheckinHit(ctx context.Context, sel ast.SelectionSe var coreImplementors = []string{"Core"} func (ec *executionContext) _Core(ctx context.Context, sel ast.SelectionSet, obj *types.Core) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, coreImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, coreImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -4852,12 +6132,12 @@ func (ec *executionContext) _Core(ctx context.Context, sel ast.SelectionSet, obj case "name": out.Values[i] = ec._Core_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "description": out.Values[i] = ec._Core_description(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "footer": field := field @@ -4869,19 +6149,19 @@ func (ec *executionContext) _Core(ctx context.Context, sel ast.SelectionSet, obj }() res = ec._Core_footer(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "domain": out.Values[i] = ec._Core_domain(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "version": out.Values[i] = ec._Core_version(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "timezone": field := field @@ -4893,7 +6173,7 @@ func (ec *executionContext) _Core(ctx context.Context, sel ast.SelectionSet, obj }() res = ec._Core_timezone(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -4907,31 +6187,31 @@ func (ec *executionContext) _Core(ctx context.Context, sel ast.SelectionSet, obj }() res = ec._Core_using_cdn(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "started_on": out.Values[i] = ec._Core_started_on(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "created_at": out.Values[i] = ec._Core_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "updated_at": out.Values[i] = ec._Core_updated_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -4940,10 +6220,10 @@ func (ec *executionContext) _Core(ctx context.Context, sel ast.SelectionSet, obj var failureImplementors = []string{"Failure"} func (ec *executionContext) _Failure(ctx context.Context, sel ast.SelectionSet, obj *types.Failure) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, failureImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, failureImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -4951,44 +6231,44 @@ func (ec *executionContext) _Failure(ctx context.Context, sel ast.SelectionSet, case "id": out.Values[i] = ec._Failure_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "issue": out.Values[i] = ec._Failure_issue(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "method": out.Values[i] = ec._Failure_method(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "method_id": out.Values[i] = ec._Failure_method_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "error_code": out.Values[i] = ec._Failure_error_code(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "ping": out.Values[i] = ec._Failure_ping(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "created_at": out.Values[i] = ec._Failure_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -4997,10 +6277,10 @@ func (ec *executionContext) _Failure(ctx context.Context, sel ast.SelectionSet, var groupImplementors = []string{"Group"} func (ec *executionContext) _Group(ctx context.Context, sel ast.SelectionSet, obj *types.Group) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, groupImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, groupImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5008,12 +6288,12 @@ func (ec *executionContext) _Group(ctx context.Context, sel ast.SelectionSet, ob case "id": out.Values[i] = ec._Group_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "name": out.Values[i] = ec._Group_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "public": field := field @@ -5025,31 +6305,31 @@ func (ec *executionContext) _Group(ctx context.Context, sel ast.SelectionSet, ob }() res = ec._Group_public(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "order_id": out.Values[i] = ec._Group_order_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "created_at": out.Values[i] = ec._Group_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "updated_at": out.Values[i] = ec._Group_updated_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5058,10 +6338,10 @@ func (ec *executionContext) _Group(ctx context.Context, sel ast.SelectionSet, ob var messageImplementors = []string{"Message"} func (ec *executionContext) _Message(ctx context.Context, sel ast.SelectionSet, obj *types.Message) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, messageImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, messageImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5069,27 +6349,27 @@ func (ec *executionContext) _Message(ctx context.Context, sel ast.SelectionSet, case "id": out.Values[i] = ec._Message_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "title": out.Values[i] = ec._Message_title(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "description": out.Values[i] = ec._Message_description(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "start_on": out.Values[i] = ec._Message_start_on(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "end_on": out.Values[i] = ec._Message_end_on(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "notify_users": field := field @@ -5101,7 +6381,7 @@ func (ec *executionContext) _Message(ctx context.Context, sel ast.SelectionSet, }() res = ec._Message_notify_users(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5115,7 +6395,7 @@ func (ec *executionContext) _Message(ctx context.Context, sel ast.SelectionSet, }() res = ec._Message_notify_method(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5129,31 +6409,31 @@ func (ec *executionContext) _Message(ctx context.Context, sel ast.SelectionSet, }() res = ec._Message_notify_before(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "notify_before_scale": out.Values[i] = ec._Message_notify_before_scale(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "created_at": out.Values[i] = ec._Message_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "updated_at": out.Values[i] = ec._Message_updated_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5162,14 +6442,14 @@ func (ec *executionContext) _Message(ctx context.Context, sel ast.SelectionSet, var queryImplementors = []string{"Query"} func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, queryImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, queryImplementors) ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ Object: "Query", }) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5206,7 +6486,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr }() res = ec._Query_services(ctx, field) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5231,7 +6511,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr }() res = ec._Query_groups(ctx, field) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5256,7 +6536,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr }() res = ec._Query_users(ctx, field) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5281,7 +6561,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr }() res = ec._Query_checkins(ctx, field) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5306,7 +6586,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr }() res = ec._Query_messages(ctx, field) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5319,7 +6599,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5328,10 +6608,10 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr var serviceImplementors = []string{"Service"} func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, obj *types.Service) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, serviceImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, serviceImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5339,17 +6619,17 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, case "id": out.Values[i] = ec._Service_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "name": out.Values[i] = ec._Service_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "domain": out.Values[i] = ec._Service_domain(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "expected": field := field @@ -5361,29 +6641,29 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_expected(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "expected_status": out.Values[i] = ec._Service_expected_status(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "interval": out.Values[i] = ec._Service_interval(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "type": out.Values[i] = ec._Service_type(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "method": out.Values[i] = ec._Service_method(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "post_data": field := field @@ -5395,24 +6675,24 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_post_data(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "port": out.Values[i] = ec._Service_port(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "timeout": out.Values[i] = ec._Service_timeout(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "order_id": out.Values[i] = ec._Service_order_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "allow_notifications": field := field @@ -5424,7 +6704,7 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_allow_notifications(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5438,7 +6718,7 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_public(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5452,7 +6732,7 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_group(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5466,7 +6746,7 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_headers(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) @@ -5480,24 +6760,24 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_permalink(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "online": out.Values[i] = ec._Service_online(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "latency": out.Values[i] = ec._Service_latency(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "ping_time": out.Values[i] = ec._Service_ping_time(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "online_24_hours": field := field @@ -5509,24 +6789,24 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, }() res = ec._Service_online_24_hours(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "avg_response": out.Values[i] = ec._Service_avg_response(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "status_code": out.Values[i] = ec._Service_status_code(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "last_success": out.Values[i] = ec._Service_last_success(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "failures": field := field @@ -5542,19 +6822,19 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, case "created_at": out.Values[i] = ec._Service_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "updated_at": out.Values[i] = ec._Service_updated_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5563,10 +6843,10 @@ func (ec *executionContext) _Service(ctx context.Context, sel ast.SelectionSet, var userImplementors = []string{"User"} func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *types.User) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, userImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, userImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5574,27 +6854,27 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._User_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "username": out.Values[i] = ec._User_username(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "email": out.Values[i] = ec._User_email(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "api_key": out.Values[i] = ec._User_api_key(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "api_secret": out.Values[i] = ec._User_api_secret(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "admin": field := field @@ -5606,26 +6886,26 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj }() res = ec._User_admin(ctx, field, obj) if res == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } return res }) case "created_at": out.Values[i] = ec._User_created_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } case "updated_at": out.Values[i] = ec._User_updated_at(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5634,10 +6914,10 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj var __DirectiveImplementors = []string{"__Directive"} func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, __DirectiveImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, __DirectiveImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5645,26 +6925,26 @@ func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionS case "name": out.Values[i] = ec.___Directive_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "description": out.Values[i] = ec.___Directive_description(ctx, field, obj) case "locations": out.Values[i] = ec.___Directive_locations(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "args": out.Values[i] = ec.___Directive_args(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5673,10 +6953,10 @@ func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionS var __EnumValueImplementors = []string{"__EnumValue"} func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.EnumValue) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, __EnumValueImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, __EnumValueImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5684,14 +6964,14 @@ func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionS case "name": out.Values[i] = ec.___EnumValue_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "description": out.Values[i] = ec.___EnumValue_description(ctx, field, obj) case "isDeprecated": out.Values[i] = ec.___EnumValue_isDeprecated(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "deprecationReason": out.Values[i] = ec.___EnumValue_deprecationReason(ctx, field, obj) @@ -5700,7 +6980,7 @@ func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionS } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5709,10 +6989,10 @@ func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionS var __FieldImplementors = []string{"__Field"} func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, obj *introspection.Field) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, __FieldImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, __FieldImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5720,24 +7000,24 @@ func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, case "name": out.Values[i] = ec.___Field_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "description": out.Values[i] = ec.___Field_description(ctx, field, obj) case "args": out.Values[i] = ec.___Field_args(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "type": out.Values[i] = ec.___Field_type(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "isDeprecated": out.Values[i] = ec.___Field_isDeprecated(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "deprecationReason": out.Values[i] = ec.___Field_deprecationReason(ctx, field, obj) @@ -5746,7 +7026,7 @@ func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5755,10 +7035,10 @@ func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, var __InputValueImplementors = []string{"__InputValue"} func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.InputValue) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, __InputValueImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, __InputValueImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5766,14 +7046,14 @@ func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.Selection case "name": out.Values[i] = ec.___InputValue_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "description": out.Values[i] = ec.___InputValue_description(ctx, field, obj) case "type": out.Values[i] = ec.___InputValue_type(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "defaultValue": out.Values[i] = ec.___InputValue_defaultValue(ctx, field, obj) @@ -5782,7 +7062,7 @@ func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.Selection } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5791,10 +7071,10 @@ func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.Selection var __SchemaImplementors = []string{"__Schema"} func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, obj *introspection.Schema) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, __SchemaImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, __SchemaImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5802,12 +7082,12 @@ func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, case "types": out.Values[i] = ec.___Schema_types(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "queryType": out.Values[i] = ec.___Schema_queryType(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "mutationType": out.Values[i] = ec.___Schema_mutationType(ctx, field, obj) @@ -5816,14 +7096,14 @@ func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, case "directives": out.Values[i] = ec.___Schema_directives(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } default: panic("unknown field " + strconv.Quote(field.Name)) } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5832,10 +7112,10 @@ func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, var __TypeImplementors = []string{"__Type"} func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, obj *introspection.Type) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, __TypeImplementors) + fields := graphql.CollectFields(ec.RequestContext, sel, __TypeImplementors) out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -5843,7 +7123,7 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o case "kind": out.Values[i] = ec.___Type_kind(ctx, field, obj) if out.Values[i] == graphql.Null { - invalid = true + invalids++ } case "name": out.Values[i] = ec.___Type_name(ctx, field, obj) @@ -5866,7 +7146,7 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o } } out.Dispatch() - if invalid { + if invalids > 0 { return graphql.Null } return out @@ -5881,7 +7161,13 @@ func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v interf } func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { - return graphql.MarshalBoolean(v) + res := graphql.MarshalBoolean(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) marshalNCheckin2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐCheckin(ctx context.Context, sel ast.SelectionSet, v []*types.Checkin) graphql.Marshaler { @@ -5926,7 +7212,13 @@ func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v inter } func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.SelectionSet, v float64) graphql.Marshaler { - return graphql.MarshalFloat(v) + res := graphql.MarshalFloat(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) marshalNGroup2githubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐGroup(ctx context.Context, sel ast.SelectionSet, v types.Group) graphql.Marshaler { @@ -5985,7 +7277,13 @@ func (ec *executionContext) unmarshalNID2int64(ctx context.Context, v interface{ } func (ec *executionContext) marshalNID2int64(ctx context.Context, sel ast.SelectionSet, v int64) graphql.Marshaler { - return graphql.MarshalInt64(v) + res := graphql.MarshalInt64(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { @@ -5993,7 +7291,13 @@ func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{} } func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler { - return graphql.MarshalInt(v) + res := graphql.MarshalInt(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) unmarshalNInt2int64(ctx context.Context, v interface{}) (int64, error) { @@ -6001,7 +7305,13 @@ func (ec *executionContext) unmarshalNInt2int64(ctx context.Context, v interface } func (ec *executionContext) marshalNInt2int64(ctx context.Context, sel ast.SelectionSet, v int64) graphql.Marshaler { - return graphql.MarshalInt64(v) + res := graphql.MarshalInt64(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) marshalNMessage2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐMessage(ctx context.Context, sel ast.SelectionSet, v []*types.Message) graphql.Marshaler { @@ -6097,7 +7407,13 @@ func (ec *executionContext) unmarshalNString2string(ctx context.Context, v inter } func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - return graphql.MarshalString(v) + res := graphql.MarshalString(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) unmarshalNTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) { @@ -6105,13 +7421,13 @@ func (ec *executionContext) unmarshalNTime2timeᚐTime(ctx context.Context, v in } func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel ast.SelectionSet, v time.Time) graphql.Marshaler { - if v.IsZero() { + res := graphql.MarshalTime(v) + if res == graphql.Null { if !ec.HasError(graphql.GetResolverContext(ctx)) { ec.Errorf(ctx, "must not be null") } - return graphql.Null } - return graphql.MarshalTime(v) + return res } func (ec *executionContext) marshalNUser2ᚕᚖgithubᚗcomᚋhunterlongᚋstatpingᚋtypesᚐUser(ctx context.Context, sel ast.SelectionSet, v []*types.User) graphql.Marshaler { @@ -6197,7 +7513,13 @@ func (ec *executionContext) unmarshalN__DirectiveLocation2string(ctx context.Con } func (ec *executionContext) marshalN__DirectiveLocation2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - return graphql.MarshalString(v) + res := graphql.MarshalString(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) unmarshalN__DirectiveLocation2ᚕstring(ctx context.Context, v interface{}) ([]string, error) { @@ -6362,7 +7684,13 @@ func (ec *executionContext) unmarshalN__TypeKind2string(ctx context.Context, v i } func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - return graphql.MarshalString(v) + res := graphql.MarshalString(v) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res } func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { diff --git a/notifiers/command.go b/notifiers/command.go index e6321077..6216e794 100644 --- a/notifiers/command.go +++ b/notifiers/command.go @@ -77,16 +77,14 @@ func (u *commandLine) Select() *notifier.Notification { // OnFailure for commandLine will trigger failing service func (u *commandLine) OnFailure(s *types.Service, f *types.Failure) { u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var2) - u.Online = false } // OnSuccess for commandLine will trigger successful service func (u *commandLine) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var1) } - u.Online = true } // OnSave for commandLine triggers when this notifier has been saved diff --git a/notifiers/command_test.go b/notifiers/command_test.go index f5f5b6d6..ffac3665 100644 --- a/notifiers/command_test.go +++ b/notifiers/command_test.go @@ -74,7 +74,7 @@ func TestCommandNotifier(t *testing.T) { }) t.Run("command Check Offline", func(t *testing.T) { - assert.False(t, command.Online) + assert.False(t, TestService.Online) }) t.Run("command OnSuccess", func(t *testing.T) { @@ -83,12 +83,12 @@ func TestCommandNotifier(t *testing.T) { }) t.Run("command Queue after being online", func(t *testing.T) { - assert.True(t, command.Online) + assert.True(t, TestService.Online) assert.Equal(t, 1, len(command.Queue)) }) t.Run("command OnSuccess Again", func(t *testing.T) { - assert.True(t, command.Online) + assert.True(t, TestService.Online) command.OnSuccess(TestService) assert.Equal(t, 1, len(command.Queue)) go notifier.Queue(command) diff --git a/notifiers/discord.go b/notifiers/discord.go index 36f43e4c..2c656149 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -71,17 +71,15 @@ func (u *discord) Select() *notifier.Notification { func (u *discord) OnFailure(s *types.Service, f *types.Failure) { msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - u.Online = false } // OnSuccess will trigger successful service func (u *discord) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } - u.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/discord_test.go b/notifiers/discord_test.go index 9f2d06ca..07bde493 100644 --- a/notifiers/discord_test.go +++ b/notifiers/discord_test.go @@ -70,7 +70,7 @@ func TestDiscordNotifier(t *testing.T) { }) t.Run("discord Check Offline", func(t *testing.T) { - assert.False(t, discorder.Online) + assert.False(t, TestService.Online) }) t.Run("discord OnSuccess", func(t *testing.T) { @@ -79,7 +79,7 @@ func TestDiscordNotifier(t *testing.T) { }) t.Run("discord Check Back Online", func(t *testing.T) { - assert.True(t, discorder.Online) + assert.True(t, TestService.Online) }) t.Run("discord OnSuccess Again", func(t *testing.T) { diff --git a/notifiers/email.go b/notifiers/email.go index 8315566b..eefa6154 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -195,12 +195,11 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) { From: u.Var1, } u.AddQueue(fmt.Sprintf("service_%v", s.Id), email) - u.Online = false } // OnSuccess will trigger successful service func (u *email) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) email := &emailOutgoing{ To: u.Var2, @@ -211,7 +210,6 @@ func (u *email) OnSuccess(s *types.Service) { } u.AddQueue(fmt.Sprintf("service_%v", s.Id), email) } - u.Online = true } func (u *email) Select() *notifier.Notification { diff --git a/notifiers/email_test.go b/notifiers/email_test.go index 9286e21c..d9d75bb3 100644 --- a/notifiers/email_test.go +++ b/notifiers/email_test.go @@ -106,7 +106,7 @@ func TestEmailNotifier(t *testing.T) { }) t.Run("email Check Offline", func(t *testing.T) { - assert.False(t, emailer.Online) + assert.False(t, TestService.Online) }) t.Run("email OnSuccess", func(t *testing.T) { @@ -115,7 +115,7 @@ func TestEmailNotifier(t *testing.T) { }) t.Run("email Check Back Online", func(t *testing.T) { - assert.True(t, emailer.Online) + assert.True(t, TestService.Online) }) t.Run("email OnSuccess Again", func(t *testing.T) { diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index c1bfaaab..454d07b2 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -74,17 +74,15 @@ func (u *lineNotifier) Select() *notifier.Notification { func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) { msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - u.Online = false } // OnSuccess will trigger successful service func (u *lineNotifier) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } - u.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/mobile.go b/notifiers/mobile.go index d6833fea..9978a78f 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -100,13 +100,12 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) { Data: data, } u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - u.Online = false } // OnSuccess will trigger successful service func (u *mobilePush) OnSuccess(s *types.Service) { data := dataJson(s, nil) - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := &pushArray{ Message: fmt.Sprintf("Your service '%v' is back online!", s.Name), @@ -116,7 +115,6 @@ func (u *mobilePush) OnSuccess(s *types.Service) { } u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } - u.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/mobile_test.go b/notifiers/mobile_test.go index 126adf7a..6c7f01a3 100644 --- a/notifiers/mobile_test.go +++ b/notifiers/mobile_test.go @@ -83,7 +83,7 @@ func TestMobileNotifier(t *testing.T) { }) t.Run("mobile Check Offline", func(t *testing.T) { - assert.False(t, mobile.Online) + assert.False(t, TestService.Online) }) t.Run("mobile OnSuccess", func(t *testing.T) { @@ -92,13 +92,13 @@ func TestMobileNotifier(t *testing.T) { }) t.Run("mobile Queue after being online", func(t *testing.T) { - assert.True(t, mobile.Online) + assert.True(t, TestService.Online) assert.Equal(t, 1, len(mobile.Queue)) }) t.Run("mobile OnSuccess Again", func(t *testing.T) { t.SkipNow() - assert.True(t, mobile.Online) + assert.True(t, TestService.Online) mobile.OnSuccess(TestService) assert.Equal(t, 1, len(mobile.Queue)) go notifier.Queue(mobile) diff --git a/notifiers/slack.go b/notifiers/slack.go index fb66f13e..14de3afa 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -108,12 +108,11 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) { Time: time.Now().Unix(), } parseSlackMessage(s.Id, failingTemplate, message) - u.Online = false } // OnSuccess will trigger successful service func (u *slack) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) message := slackMessage{ Service: s, @@ -122,7 +121,6 @@ func (u *slack) OnSuccess(s *types.Service) { } parseSlackMessage(s.Id, successTemplate, message) } - u.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/slack_test.go b/notifiers/slack_test.go index 43dc118e..4e0bc07a 100644 --- a/notifiers/slack_test.go +++ b/notifiers/slack_test.go @@ -86,7 +86,7 @@ func TestSlackNotifier(t *testing.T) { }) t.Run("slack Check Offline", func(t *testing.T) { - assert.False(t, slacker.Online) + assert.False(t, TestService.Online) }) t.Run("slack OnSuccess", func(t *testing.T) { @@ -95,12 +95,12 @@ func TestSlackNotifier(t *testing.T) { }) t.Run("slack Queue after being online", func(t *testing.T) { - assert.True(t, slacker.Online) + assert.True(t, TestService.Online) assert.Equal(t, 1, len(slacker.Queue)) }) t.Run("slack OnSuccess Again", func(t *testing.T) { - assert.True(t, slacker.Online) + assert.True(t, TestService.Online) slacker.OnSuccess(TestService) assert.Equal(t, 1, len(slacker.Queue)) go notifier.Queue(slacker) diff --git a/notifiers/telegram.go b/notifiers/telegram.go index da69ebd1..842bbc72 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -93,17 +93,15 @@ func (u *telegram) Send(msg interface{}) error { func (u *telegram) OnFailure(s *types.Service, f *types.Failure) { msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - u.Online = false } // OnSuccess will trigger successful service func (u *telegram) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } - u.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/telegram_test.go b/notifiers/telegram_test.go index 50aa7550..7bc836db 100644 --- a/notifiers/telegram_test.go +++ b/notifiers/telegram_test.go @@ -71,7 +71,7 @@ func TestTelegramNotifier(t *testing.T) { }) t.Run("Telegram Check Offline", func(t *testing.T) { - assert.False(t, telegramNotifier.Online) + assert.False(t, TestService.Online) }) t.Run("Telegram OnSuccess", func(t *testing.T) { @@ -80,7 +80,7 @@ func TestTelegramNotifier(t *testing.T) { }) t.Run("Telegram Check Back Online", func(t *testing.T) { - assert.True(t, telegramNotifier.Online) + assert.True(t, TestService.Online) }) t.Run("Telegram OnSuccess Again", func(t *testing.T) { diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 1ea65fe5..61b4dcf4 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -103,17 +103,15 @@ func (u *twilio) Send(msg interface{}) error { func (u *twilio) OnFailure(s *types.Service, f *types.Failure) { msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - u.Online = false } // OnSuccess will trigger successful service func (u *twilio) OnSuccess(s *types.Service) { - if !u.Online { + if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } - u.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/twilio_test.go b/notifiers/twilio_test.go index 4b7fe1b2..9f347591 100644 --- a/notifiers/twilio_test.go +++ b/notifiers/twilio_test.go @@ -77,7 +77,7 @@ func TestTwilioNotifier(t *testing.T) { }) t.Run("Twilio Check Offline", func(t *testing.T) { - assert.False(t, twilioNotifier.Online) + assert.False(t, TestService.Online) }) t.Run("Twilio OnSuccess", func(t *testing.T) { @@ -86,7 +86,7 @@ func TestTwilioNotifier(t *testing.T) { }) t.Run("Twilio Check Back Online", func(t *testing.T) { - assert.True(t, twilioNotifier.Online) + assert.True(t, TestService.Online) }) t.Run("Twilio OnSuccess Again", func(t *testing.T) { diff --git a/notifiers/webhook.go b/notifiers/webhook.go index ec63d8a1..d4136ccf 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -166,17 +166,15 @@ func (w *webhooker) OnTest() error { func (w *webhooker) OnFailure(s *types.Service, f *types.Failure) { msg := replaceBodyText(w.Var2, s, f) w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - w.Online = false } // OnSuccess will trigger successful service func (w *webhooker) OnSuccess(s *types.Service) { - if !w.Online { + if !s.Online { w.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := replaceBodyText(w.Var2, s, nil) w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } - w.Online = true } // OnSave triggers when this notifier has been saved diff --git a/notifiers/webhook_test.go b/notifiers/webhook_test.go index d21a7bd7..a618b3c2 100644 --- a/notifiers/webhook_test.go +++ b/notifiers/webhook_test.go @@ -80,7 +80,7 @@ func TestWebhookNotifier(t *testing.T) { }) t.Run("webhooker Check Offline", func(t *testing.T) { - assert.False(t, webhook.Online) + assert.False(t, TestService.Online) }) t.Run("webhooker OnSuccess", func(t *testing.T) { @@ -89,7 +89,7 @@ func TestWebhookNotifier(t *testing.T) { }) t.Run("webhooker Check Back Online", func(t *testing.T) { - assert.True(t, webhook.Online) + assert.True(t, TestService.Online) }) t.Run("webhooker OnSuccess Again", func(t *testing.T) { diff --git a/source/css/base.css b/source/css/base.css index 9add7225..255f76fc 100644 --- a/source/css/base.css +++ b/source/css/base.css @@ -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 */ diff --git a/source/wiki.go b/source/wiki.go index 01f20a1d..3613d1f4 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,9 +1,9 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-06-24 15:19:59.865474 -0700 PDT m=+0.786948198 -// +// 2019-08-16 02:17:31.047416 +0300 +03 m=+1.970455580 +// // This contains the most recently Markdown source for the Statping Wiki. package source // CompiledWiki contains all of the Statping Wiki pages from the Github Wiki repo. -var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintence\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASS=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") +var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintence\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASS=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") \ No newline at end of file From 50ba485fcdc1d05293308a9fb949955becab1cc6 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 3 Sep 2019 14:10:13 -0700 Subject: [PATCH 003/100] fixed tests - removed database creds from logs --- Gopkg.lock | 103 +++++----- Gopkg.toml | 10 +- core/checker.go | 6 +- core/database.go | 4 +- core/services_test.go | 9 +- notifiers/command_test.go | 19 +- notifiers/discord_test.go | 4 - notifiers/email_test.go | 4 - notifiers/mobile_test.go | 15 +- notifiers/notifiers_test.go | 1 + notifiers/slack_test.go | 20 +- notifiers/twilio_test.go | 1 - notifiers/webhook_test.go | 6 +- source/css/base.css | 384 +++++++++++++++++++++++------------- source/wiki.go | 6 +- types/time.go | 8 +- version.txt | 2 +- 17 files changed, 337 insertions(+), 265 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 3507558c..d45192e8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,8 +2,7 @@ [[projects]] - branch = "master" - digest = "1:a2de8c02990ea7cb28b962d2effa6840cc2c849b298736eef57a9713432cc0d3" + digest = "1:d4bfd57449b0bdfe927ec45c8463afd8f5b6012d4bcd5a9da8581a408c23e57c" name = "github.com/99designs/gqlgen" packages = [ "complexity", @@ -12,7 +11,8 @@ "handler", ] pruneopts = "UT" - revision = "afe33f73875beca92e917742c1f49c1f6145018b" + revision = "a7bc468ca1b184a5ce1b07ea331e0121fc56ae82" + version = "v0.9.3" [[projects]] digest = "1:07f7314344b2771963ada0b2a4a426c59d782dac227dcfff2499188a186446c0" @@ -42,12 +42,12 @@ version = "0.0.1" [[projects]] - digest = "1:86b9f9c54fcddec313009ef19073bf0124bbdae3701080c32f67c0c8817b3b7c" + digest = "1:8e8da6cc8cca12851d4e089d970a0f7387b3a6bcc8c459ff432213b03076a66d" name = "github.com/daaku/go.zipexe" packages = ["."] pruneopts = "UT" - revision = "db7cf2ba330f8c2d28b827826e33d6628ea7e9e0" - version = "v1.0.0" + revision = "74d766ac1dde7458348221869a7d1e7e5fa0597e" + version = "v1.0.1" [[projects]] digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" @@ -82,20 +82,12 @@ version = "v2.2.2" [[projects]] - digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1" - name = "github.com/gorilla/context" - packages = ["."] - pruneopts = "UT" - revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" - version = "v1.1.1" - -[[projects]] - digest = "1:d5f97fc268267ec1b61c3453058c738246fc3e746f14b1ae25161513b7367b0c" + digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296" name = "github.com/gorilla/mux" packages = ["."] pruneopts = "UT" - revision = "c5c6c98bc25355028a63748a498942a6398ccd22" - version = "v1.7.1" + revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15" + version = "v1.7.3" [[projects]] digest = "1:e72d1ebb8d395cf9f346fd9cbc652e5ae222dd85e0ac842dc57f175abed6d195" @@ -106,34 +98,34 @@ version = "v1.1.1" [[projects]] - digest = "1:e5bf52fd66a2e984b57b4c0f2c4ee024ed749a19886246240629998dc0cf31ce" + digest = "1:172c862eabc72e90f461bcef223c49869628bec6d989386dfb03281ae3222148" name = "github.com/gorilla/sessions" packages = ["."] pruneopts = "UT" - revision = "f57b7e2d29c6211d16ffa52a0998272f75799030" - version = "v1.1.3" + revision = "4355a998706e83fe1d71c31b07af94e34f68d74a" + version = "v1.2.0" [[projects]] - digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d" + digest = "1:e62657cca9badaa308d86e7716083e4c5933bb78e30a17743fc67f50be26f6f4" name = "github.com/gorilla/websocket" packages = ["."] pruneopts = "UT" - revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d" - version = "v1.4.0" + revision = "c3e18be99d19e6b3e8f1559eea2c161a665c4b6b" + version = "v1.4.1" [[projects]] - digest = "1:d15ee511aa0f56baacc1eb4c6b922fa1c03b38413b6be18166b996d82a0156ea" + digest = "1:c77361e611524ec8f2ad37c408c3c916111a70b6acf806a1200855696bf8fa4d" name = "github.com/hashicorp/golang-lru" packages = [ ".", "simplelru", ] pruneopts = "UT" - revision = "7087cb70de9f7a8bc0a10c375cb0d2280a8edf9c" - version = "v0.5.1" + revision = "7f827b33c0f158ec5dfbba01bb0b14a4541fd81d" + version = "v0.5.3" [[projects]] - digest = "1:0731b955911f880c75409845b54203f5127c72e003691e2d34462a0516a9b1f6" + digest = "1:b0c1770be8c52cf00117b98049de1e4df91c8df588102198364b09669bb60178" name = "github.com/jinzhu/gorm" packages = [ ".", @@ -142,16 +134,16 @@ "dialects/sqlite", ] pruneopts = "UT" - revision = "e3987fd4b803c16497aa4dfd2e75db7a6a061a4e" - version = "v1.9.4" + revision = "836fb2c19d84dac7b0272958dfb9af7cf0d0ade4" + version = "v1.9.10" [[projects]] - branch = "master" - digest = "1:fd97437fbb6b7dce04132cf06775bd258cce305c44add58eb55ca86c6c325160" + digest = "1:01ed62f8f4f574d8aff1d88caee113700a2b44c42351943fa73cc1808f736a50" name = "github.com/jinzhu/inflection" packages = ["."] pruneopts = "UT" - revision = "04140366298a54a039076d798123ffa108fff46c" + revision = "f5c5f50e6090ae76a29240b61ae2a90dd810112e" + version = "v1.0.0" [[projects]] digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b" @@ -162,7 +154,7 @@ version = "v1.3.0" [[projects]] - digest = "1:226be3582c304c347481157049c862924fdd6277256e854781c5ba4728901215" + digest = "1:0ead8e64fe356bd9221605e3ec40b4438509868018cbbbaaaff3ebae1b69b78b" name = "github.com/lib/pq" packages = [ ".", @@ -171,16 +163,16 @@ "scram", ] pruneopts = "UT" - revision = "51e2106eed1cea199c802d2a49e91e2491b02056" - version = "v1.1.0" + revision = "3427c32cb71afc948325f299f040e53c1dd78979" + version = "v1.2.0" [[projects]] - digest = "1:4a49346ca45376a2bba679ca0e83bec949d780d4e927931317904bad482943ec" + digest = "1:79e87abf06b873987dee86598950f5b51732ac454d5a5cab6445a14330e6c9e3" name = "github.com/mattn/go-sqlite3" packages = ["."] pruneopts = "UT" - revision = "c7c4067b79cc51e6dfdcef5c702e74b1e0fa7c75" - version = "v1.10.0" + revision = "b612a2feea6aa87c6d052d9086572551df06497e" + version = "v1.11.0" [[projects]] digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" @@ -207,12 +199,12 @@ version = "v1.0.0" [[projects]] - digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759" + digest = "1:8548c309c65a85933a625be5e7d52b6ac927ca30c56869fae58123b8a77a75e1" name = "github.com/stretchr/testify" packages = ["assert"] pruneopts = "UT" - revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" - version = "v1.3.0" + revision = "221dbe5ed46703ee255b1da0dec05086f5035f62" + version = "v1.4.0" [[projects]] branch = "master" @@ -247,11 +239,11 @@ "blowfish", ] pruneopts = "UT" - revision = "a29dc8fdc73485234dbef99ebedb95d2eced08de" + revision = "9756ffdc24725223350eb3266ffb92590d28f278" [[projects]] branch = "master" - digest = "1:5e22b2014e7cd102e2b41ab7dd38b9c5175e3447b653033987164bebc858e958" + digest = "1:caffb9a4f8c756941de4b3eb577abd167e7fd4b570f2078c05ceb8835a1514cb" name = "golang.org/x/net" packages = [ "bpf", @@ -262,15 +254,26 @@ "ipv6", ] pruneopts = "UT" - revision = "4829fb13d2c62012c17688fa7f629f371014946d" + revision = "ba9fcec4b297b415637633c5a6e8fa592e4a16c3" + +[[projects]] + branch = "master" + digest = "1:d94059c196c160bd1c4030d49ffaa39a456be516501e5916bea663f5d79a75ec" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "UT" + revision = "9109b7679e13aa34a54834cfb4949cac4b96e576" [[projects]] digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" name = "google.golang.org/appengine" packages = ["cloudsql"] pruneopts = "UT" - revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610" - version = "v1.5.0" + revision = "5f2a59506353b8d5ba8cbbcd9f3c1f41f1eaf079" + version = "v1.6.2" [[projects]] branch = "v3" @@ -296,6 +299,14 @@ revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135" version = "v2.0.1" +[[projects]] + digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 015fd3f8..402837b4 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,7 +27,7 @@ [[constraint]] name = "github.com/99designs/gqlgen" - branch = "master" + version = "0.9.3" [[constraint]] name = "github.com/GeertJohan/go.rice" @@ -47,15 +47,15 @@ [[constraint]] name = "github.com/gorilla/mux" - version = "1.7.1" + version = "1.7.3" [[constraint]] name = "github.com/gorilla/sessions" - version = "1.1.3" + version = "1.2.0" [[constraint]] name = "github.com/jinzhu/gorm" - version = "1.9.4" + version = "1.9.10" [[constraint]] name = "github.com/joho/godotenv" @@ -67,7 +67,7 @@ [[constraint]] name = "github.com/stretchr/testify" - version = "1.3.0" + version = "1.4.0" [[constraint]] branch = "master" diff --git a/core/checker.go b/core/checker.go index 7f6ce2fd..53714630 100644 --- a/core/checker.go +++ b/core/checker.go @@ -40,6 +40,7 @@ func checkServices() { } // Check will run checkHttp for HTTP services and checkTcp for TCP services +// if record param is set to true, it will add a record into the database. func (s *Service) Check(record bool) { switch s.Type { case "http": @@ -260,8 +261,8 @@ 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) - notifier.OnSuccess(s.Service) s.Online = true + notifier.OnSuccess(s.Service) } // recordFailure will create a new 'Failure' record in the database for a offline service @@ -275,7 +276,6 @@ 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) - notifier.OnFailure(s.Service, fail.Failure) s.Online = false - + notifier.OnFailure(s.Service, fail.Failure) } diff --git a/core/database.go b/core/database.go index e176ea94..1bdbb90e 100644 --- a/core/database.go +++ b/core/database.go @@ -217,7 +217,7 @@ func (db *DbConfig) Connect(retry bool, location string) error { dbSession, err := gorm.Open(dbType, conn) if err != nil { if retry { - utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", conn)) + utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", Configs.DbHost)) return db.waitForDb() } else { return err @@ -229,7 +229,7 @@ func (db *DbConfig) Connect(retry bool, location string) error { err = dbSession.DB().Ping() if err == nil { DbSession = dbSession - utils.Log(1, fmt.Sprintf("Database %v connection '%v@%v:%v' at %v was successful.", dbType, Configs.DbUser, Configs.DbHost, Configs.DbPort, Configs.DbData)) + utils.Log(1, fmt.Sprintf("Database %v connection was successful.", dbType)) } return err } diff --git a/core/services_test.go b/core/services_test.go index c437a615..a8f7aa4e 100644 --- a/core/services_test.go +++ b/core/services_test.go @@ -64,18 +64,15 @@ func TestSelectTCPService(t *testing.T) { func TestUpdateService(t *testing.T) { service := SelectService(1) - service2 := SelectService(2) assert.Equal(t, "Google", service.Name) - assert.Equal(t, "Statping Github", service2.Name) - assert.True(t, service.Online) - assert.True(t, service2.Online) service.Name = "Updated Google" service.Interval = 5 err := service.Update(true) assert.Nil(t, err) // check if updating pointer array shutdown any other service - service2 = SelectService(2) - assert.True(t, service2.Online) + service = SelectService(1) + assert.Equal(t, "Updated Google", service.Name) + assert.Equal(t, 5, service.Interval) } func TestUpdateAllServices(t *testing.T) { diff --git a/notifiers/command_test.go b/notifiers/command_test.go index ffac3665..f6ed7314 100644 --- a/notifiers/command_test.go +++ b/notifiers/command_test.go @@ -66,33 +66,16 @@ func TestCommandNotifier(t *testing.T) { assert.Equal(t, 1, len(command.Queue)) }) - t.Run("command OnFailure multiple times", func(t *testing.T) { - for i := 0; i <= 50; i++ { - command.OnFailure(TestService, TestFailure) - } - assert.Equal(t, 52, len(command.Queue)) - }) - - t.Run("command Check Offline", func(t *testing.T) { - assert.False(t, TestService.Online) - }) - t.Run("command OnSuccess", func(t *testing.T) { command.OnSuccess(TestService) assert.Equal(t, 1, len(command.Queue)) }) - t.Run("command Queue after being online", func(t *testing.T) { - assert.True(t, TestService.Online) - assert.Equal(t, 1, len(command.Queue)) - }) - t.Run("command OnSuccess Again", func(t *testing.T) { - assert.True(t, TestService.Online) command.OnSuccess(TestService) assert.Equal(t, 1, len(command.Queue)) go notifier.Queue(command) - time.Sleep(5 * time.Second) + time.Sleep(20 * time.Second) assert.Equal(t, 0, len(command.Queue)) }) diff --git a/notifiers/discord_test.go b/notifiers/discord_test.go index 07bde493..02e4af9d 100644 --- a/notifiers/discord_test.go +++ b/notifiers/discord_test.go @@ -69,10 +69,6 @@ func TestDiscordNotifier(t *testing.T) { assert.Equal(t, 1, len(discorder.Queue)) }) - t.Run("discord Check Offline", func(t *testing.T) { - assert.False(t, TestService.Online) - }) - t.Run("discord OnSuccess", func(t *testing.T) { discorder.OnSuccess(TestService) assert.Equal(t, 1, len(discorder.Queue)) diff --git a/notifiers/email_test.go b/notifiers/email_test.go index d9d75bb3..0e7b3284 100644 --- a/notifiers/email_test.go +++ b/notifiers/email_test.go @@ -105,10 +105,6 @@ func TestEmailNotifier(t *testing.T) { assert.Equal(t, 1, len(emailer.Queue)) }) - t.Run("email Check Offline", func(t *testing.T) { - assert.False(t, TestService.Online) - }) - t.Run("email OnSuccess", func(t *testing.T) { emailer.OnSuccess(TestService) assert.Equal(t, 1, len(emailer.Queue)) diff --git a/notifiers/mobile_test.go b/notifiers/mobile_test.go index 6c7f01a3..2e56131f 100644 --- a/notifiers/mobile_test.go +++ b/notifiers/mobile_test.go @@ -76,24 +76,15 @@ func TestMobileNotifier(t *testing.T) { }) t.Run("mobile OnFailure multiple times", func(t *testing.T) { - for i := 0; i <= 50; i++ { + for i := 0; i <= 5; i++ { mobile.OnFailure(TestService, TestFailure) } - assert.Equal(t, 52, len(mobile.Queue)) - }) - - t.Run("mobile Check Offline", func(t *testing.T) { - assert.False(t, TestService.Online) + assert.Equal(t, 7, len(mobile.Queue)) }) t.Run("mobile OnSuccess", func(t *testing.T) { mobile.OnSuccess(TestService) - assert.Equal(t, 1, len(mobile.Queue)) - }) - - t.Run("mobile Queue after being online", func(t *testing.T) { - assert.True(t, TestService.Online) - assert.Equal(t, 1, len(mobile.Queue)) + assert.Equal(t, 7, len(mobile.Queue)) }) t.Run("mobile OnSuccess Again", func(t *testing.T) { diff --git a/notifiers/notifiers_test.go b/notifiers/notifiers_test.go index 98a9e6fb..2550f5aa 100644 --- a/notifiers/notifiers_test.go +++ b/notifiers/notifiers_test.go @@ -42,6 +42,7 @@ var TestService = &types.Service{ Method: "GET", Timeout: 20, LastStatusCode: 404, + Online: true, LastResponse: "this is an example response", CreatedAt: time.Now().Add(-24 * time.Hour), } diff --git a/notifiers/slack_test.go b/notifiers/slack_test.go index 4e0bc07a..f5e37175 100644 --- a/notifiers/slack_test.go +++ b/notifiers/slack_test.go @@ -78,33 +78,17 @@ func TestSlackNotifier(t *testing.T) { assert.Equal(t, 1, len(slacker.Queue)) }) - t.Run("slack OnFailure multiple times", func(t *testing.T) { - for i := 0; i <= 50; i++ { - slacker.OnFailure(TestService, TestFailure) - } - assert.Equal(t, 52, len(slacker.Queue)) - }) - - t.Run("slack Check Offline", func(t *testing.T) { - assert.False(t, TestService.Online) - }) - t.Run("slack OnSuccess", func(t *testing.T) { slacker.OnSuccess(TestService) assert.Equal(t, 1, len(slacker.Queue)) }) - t.Run("slack Queue after being online", func(t *testing.T) { - assert.True(t, TestService.Online) - assert.Equal(t, 1, len(slacker.Queue)) - }) - t.Run("slack OnSuccess Again", func(t *testing.T) { assert.True(t, TestService.Online) slacker.OnSuccess(TestService) assert.Equal(t, 1, len(slacker.Queue)) go notifier.Queue(slacker) - time.Sleep(6 * time.Second) + time.Sleep(15 * time.Second) assert.Equal(t, 0, len(slacker.Queue)) }) @@ -127,7 +111,7 @@ func TestSlackNotifier(t *testing.T) { t.Run("slack Queue", func(t *testing.T) { go notifier.Queue(slacker) - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) assert.Equal(t, SLACK_URL, slacker.Host) assert.Equal(t, 0, len(slacker.Queue)) }) diff --git a/notifiers/twilio_test.go b/notifiers/twilio_test.go index 9f347591..11e9e47f 100644 --- a/notifiers/twilio_test.go +++ b/notifiers/twilio_test.go @@ -45,7 +45,6 @@ func init() { func TestTwilioNotifier(t *testing.T) { t.SkipNow() - t.Parallel() if TWILIO_SID == "" || TWILIO_SECRET == "" || TWILIO_FROM == "" { t.Log("twilio notifier testing skipped, missing TWILIO_SID environment variable") t.SkipNow() diff --git a/notifiers/webhook_test.go b/notifiers/webhook_test.go index a618b3c2..3ac57715 100644 --- a/notifiers/webhook_test.go +++ b/notifiers/webhook_test.go @@ -65,7 +65,7 @@ func TestWebhookNotifier(t *testing.T) { t.Run("webhooker Replace Body Text", func(t *testing.T) { fullMsg = replaceBodyText(webhookMessage, TestService, TestFailure) - assert.Equal(t, `{"id": "1","name": "Interpol - All The Rage Back Home","online": "false","issue": "testing"}`, fullMsg) + assert.Equal(t, `{"id": "1","name": "Interpol - All The Rage Back Home","online": "true","issue": "testing"}`, fullMsg) }) t.Run("webhooker Within Limits", func(t *testing.T) { @@ -79,10 +79,6 @@ func TestWebhookNotifier(t *testing.T) { assert.Len(t, webhook.Queue, 1) }) - t.Run("webhooker Check Offline", func(t *testing.T) { - assert.False(t, TestService.Online) - }) - t.Run("webhooker OnSuccess", func(t *testing.T) { webhook.OnSuccess(TestService) assert.Equal(t, len(webhook.Queue), 1) diff --git a/source/css/base.css b/source/css/base.css index 255f76fc..9add7225 100644 --- a/source/css/base.css +++ b/source/css/base.css @@ -7,54 +7,66 @@ /* 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; @@ -62,82 +74,101 @@ 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 .75rem; - font-size: 0.8rem; } + height: 25px; +} +.btn-group A { + padding: 0.1rem 0.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: .13rem; } + padding-top: 0.13rem; +} input.inputTags-field:focus { - outline-width: 0; } + outline-width: 0; +} .inputTags-list { display: block; width: 100%; min-height: calc(2.25rem + 2px); - padding: .2rem .35rem; + padding: 0.2rem 0.35rem; font-size: 1rem; font-weight: 400; line-height: 1.5; @@ -145,8 +176,9 @@ input.inputTags-field:focus { background-color: #fff; background-clip: padding-box; border: 1px solid #ced4da; - border-radius: .25rem; - transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; } + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} .inputTags-item { background-color: #3aba39; @@ -154,63 +186,81 @@ 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 */ @@ -230,23 +280,26 @@ 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 .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.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 .15s, box-shadow ease-in-out .15s; } + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} .switch { font-size: 1rem; - position: relative; } + position: relative; +} .switch input { position: absolute; @@ -257,7 +310,8 @@ 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; @@ -270,23 +324,26 @@ 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; @@ -295,105 +352,137 @@ 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 { @@ -405,10 +494,12 @@ 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; @@ -422,112 +513,139 @@ 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 */ diff --git a/source/wiki.go b/source/wiki.go index 3613d1f4..f318f249 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,9 +1,9 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-08-16 02:17:31.047416 +0300 +03 m=+1.970455580 -// +// 2019-09-03 14:03:08.871368 -0700 PDT m=+0.844240186 +// // This contains the most recently Markdown source for the Statping Wiki. package source // CompiledWiki contains all of the Statping Wiki pages from the Github Wiki repo. -var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintence\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASS=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") \ No newline at end of file +var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintence\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") diff --git a/types/time.go b/types/time.go index b7ededd9..d4e6aef6 100644 --- a/types/time.go +++ b/types/time.go @@ -20,10 +20,10 @@ import ( ) const ( - TIME_NANO = "2006-01-02T15:04:05Z" - TIME = "2006-01-02 15:04:05" - CHART_TIME = "2006-01-02T15:04:05.999999-07:00" - TIME_DAY = "2006-01-02" + TIME_NANO = "2006-01-02T15:04:05Z" + TIME = "2006-01-02 15:04:05" + CHART_TIME = "2006-01-02T15:04:05.999999-07:00" + TIME_DAY = "2006-01-02" ) var ( diff --git a/version.txt b/version.txt index 5691351f..e1259258 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.80.62 +0.80.63 From a19b5dcac889e5f2fa435986505fd5f8e2fb1143 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 3 Sep 2019 14:28:54 -0700 Subject: [PATCH 004/100] telegram test --- notifiers/telegram_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/notifiers/telegram_test.go b/notifiers/telegram_test.go index 7bc836db..6ed7ff8c 100644 --- a/notifiers/telegram_test.go +++ b/notifiers/telegram_test.go @@ -37,6 +37,7 @@ func init() { } func TestTelegramNotifier(t *testing.T) { + t.SkipNow() t.Parallel() if telegramToken == "" || telegramChannel == "" { t.Log("Telegram notifier testing skipped, missing TELEGRAM_TOKEN and TELEGRAM_CHANNEL environment variable") From 4f86749b64d1f219c885f885584a0272faca0e1a Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Wed, 4 Sep 2019 21:38:56 +0300 Subject: [PATCH 005/100] a workaroung for golang limitation of passing Host in header --- utils/utils.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 53c53efc..74706d64 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -301,7 +301,11 @@ func HttpRequest(url, method string, content interface{}, headers []string, body keyVal := strings.Split(h, "=") if len(keyVal) == 2 { if keyVal[0] != "" && keyVal[1] != "" { - req.Header.Set(keyVal[0], keyVal[1]) + if strings.ToLower(keyVal[0]) == "host" { + req.Host = strings.TrimSpace(keyVal[1]) + } else { + req.Header.Set(keyVal[0], keyVal[1]) + } } } } From 4da56523f31a694aa885920e7874e8bc633548e9 Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Fri, 6 Sep 2019 00:48:27 +0300 Subject: [PATCH 006/100] bypass notifier limits if the state is changing first time with that event. --- core/notifier/events.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/notifier/events.go b/core/notifier/events.go index 3f0fdd1c..05a52287 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -35,7 +35,7 @@ func OnFailure(s *types.Service, f *types.Failure) { return } for _, comm := range AllCommunications { - if isType(comm, new(BasicEvents)) && isEnabled(comm) && inLimits(comm) { + if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { comm.(BasicEvents).OnFailure(s, f) } } @@ -48,7 +48,7 @@ func OnSuccess(s *types.Service) { return } for _, comm := range AllCommunications { - if isType(comm, new(BasicEvents)) && isEnabled(comm) && inLimits(comm) { + if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) { comm.(BasicEvents).OnSuccess(s) } } From 7abc2e6253c8c025d1657c8e4b52df2456d6133d Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Sun, 8 Sep 2019 16:11:09 +0300 Subject: [PATCH 007/100] fix back online scenario we broke in pull request #234 --- core/checker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/checker.go b/core/checker.go index 53714630..c79a3f7c 100644 --- a/core/checker.go +++ b/core/checker.go @@ -261,8 +261,8 @@ 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 } // recordFailure will create a new 'Failure' record in the database for a offline service @@ -276,6 +276,6 @@ 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 notifier.OnFailure(s.Service, fail.Failure) + s.Online = false } From b64d0ee1f143160d281204642d4748d148a019fd Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Tue, 17 Sep 2019 13:18:12 +0300 Subject: [PATCH 008/100] do ssl verification from host header and made it optional per service --- cmd/cli.go | 2 +- core/checker.go | 4 ++-- notifiers/discord.go | 4 ++-- notifiers/line_notify.go | 2 +- notifiers/mobile.go | 2 +- notifiers/slack.go | 5 +++-- notifiers/telegram.go | 2 +- notifiers/twilio.go | 2 +- source/tmpl/form_service.gohtml | 10 ++++++++++ types/service.go | 1 + utils/utils.go | 31 +++++++++++++++++-------------- 11 files changed, 40 insertions(+), 25 deletions(-) diff --git a/cmd/cli.go b/cmd/cli.go index 2f443be1..d39d3ad4 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -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 } diff --git a/core/checker.go b/core/checker.go index 53714630..6eff907e 100644 --- a/core/checker.go +++ b/core/checker.go @@ -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 { diff --git a/notifiers/discord.go b/notifiers/discord.go index 2c656149..82cb1c70 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -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 } @@ -93,7 +93,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 } diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index 454d07b2..4e5cd3f8 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -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 } diff --git a/notifiers/mobile.go b/notifiers/mobile.go index 9978a78f..3cceebf1 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -176,7 +176,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 } diff --git a/notifiers/slack.go b/notifiers/slack.go index 14de3afa..7c435ef2 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -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") } diff --git a/notifiers/telegram.go b/notifiers/telegram.go index 842bbc72..000b3292 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -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 { diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 61b4dcf4..6dcf9fbd 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -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) diff --git a/source/tmpl/form_service.gohtml b/source/tmpl/form_service.gohtml index 053477f0..f9ee9376 100644 --- a/source/tmpl/form_service.gohtml +++ b/source/tmpl/form_service.gohtml @@ -108,6 +108,16 @@ You can also drag and drop services to reorder on the Services tab. +
+ +
+ + + + + +
+
diff --git a/types/service.go b/types/service.go index ae5979a1..f0435dba 100644 --- a/types/service.go +++ b/types/service.go @@ -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"` diff --git a/utils/utils.go b/utils/utils.go index 74706d64..348cb02f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -274,21 +274,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 +297,22 @@ func HttpRequest(url, method string, content interface{}, headers []string, body } } var resp *http.Response + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: !verifySSL, + ServerName: req.Host, + }, + DisableKeepAlives: true, + ResponseHeaderTimeout: timeout, + TLSHandshakeTimeout: timeout, + Proxy: http.ProxyFromEnvironment, + } + client := &http.Client{ + Transport: transport, + Timeout: timeout, + } + if resp, err = client.Do(req); err != nil { return nil, resp, err } From d83646e54f371f697fe7b298a98c9b4a51b21018 Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Tue, 17 Sep 2019 13:18:40 +0300 Subject: [PATCH 009/100] add error message to slack notification --- notifiers/slack.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notifiers/slack.go b/notifiers/slack.go index 7c435ef2..3819e0e0 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -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":"{{.}}"}` ) @@ -107,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) } From bd58f3c7476021e0343749849411f511ab9a93f5 Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Wed, 18 Sep 2019 13:10:53 +0300 Subject: [PATCH 010/100] force dialer to connect the host specified in req.URL.Host not in req.Host! --- utils/utils.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index 348cb02f..91a64bda 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -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" @@ -298,6 +300,11 @@ func HttpRequest(url, method string, content interface{}, headers []string, body } var resp *http.Response + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + } + transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: !verifySSL, @@ -307,6 +314,11 @@ func HttpRequest(url, method string, content interface{}, headers []string, body 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 = req.URL.Host + return dialer.DialContext(ctx, network, addr) + }, } client := &http.Client{ Transport: transport, From 1140dfa2133b481759c957fddf2c5a5780bf0d1a Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Wed, 18 Sep 2019 13:29:44 +0300 Subject: [PATCH 011/100] add dialer port --- utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 91a64bda..b71d4569 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -316,7 +316,7 @@ func HttpRequest(url, method string, content interface{}, headers []string, body Proxy: http.ProxyFromEnvironment, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { // redirect all connections to host specified in url - addr = req.URL.Host + addr = strings.Split(req.URL.Host, ":")[0] + addr[strings.LastIndex(addr, ":"):] return dialer.DialContext(ctx, network, addr) }, } From a6718f884308b17f7bf40d85dee923f2cc4a833e Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Wed, 18 Sep 2019 13:39:10 +0300 Subject: [PATCH 012/100] use user defined timeout instead --- utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index b71d4569..2d7fcd94 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -301,8 +301,8 @@ func HttpRequest(url, method string, content interface{}, headers []string, body var resp *http.Response dialer := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, + Timeout: timeout * time.Second, + KeepAlive: timeout * time.Second, } transport := &http.Transport{ From 9d9f91f9317e41f7861d6b7f6c3ddb137302826c Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Thu, 19 Sep 2019 22:27:01 -0700 Subject: [PATCH 013/100] fixed homepage graphs rendering - added notifier events in logs --- core/notifier/events.go | 18 +- notifiers/email.go | 2 +- source/css/base.css | 384 ++++++++++++++-------------------------- source/js/main.js | 3 + source/wiki.go | 4 +- version.txt | 2 +- 6 files changed, 157 insertions(+), 256 deletions(-) diff --git a/core/notifier/events.go b/core/notifier/events.go index 3f0fdd1c..de9eb1b5 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -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) { @@ -36,6 +40,8 @@ func OnFailure(s *types.Service, f *types.Failure) { } for _, comm := range AllCommunications { if isType(comm, new(BasicEvents)) && isEnabled(comm) && 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) } } @@ -49,6 +55,8 @@ func OnSuccess(s *types.Service) { } for _, comm := range AllCommunications { if isType(comm, new(BasicEvents)) && isEnabled(comm) && 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) } } @@ -59,6 +67,7 @@ func OnSuccess(s *types.Service) { 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 +80,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 +93,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 +103,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 +113,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 +123,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 +133,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 +161,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) } } diff --git a/notifiers/email.go b/notifiers/email.go index eefa6154..1c06dd50 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -264,7 +264,7 @@ func (u *email) OnTest() error { Data: interface{}(testService), From: u.Var1, } - err = u.Send(email) + err = u.dialSend(email) return err } diff --git a/source/css/base.css b/source/css/base.css index 9add7225..255f76fc 100644 --- a/source/css/base.css +++ b/source/css/base.css @@ -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 */ diff --git a/source/js/main.js b/source/js/main.js index b2c63ebc..3ddf566d 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -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); diff --git a/source/wiki.go b/source/wiki.go index f318f249..ae38364c 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,9 +1,9 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-09-03 14:03:08.871368 -0700 PDT m=+0.844240186 +// 2019-09-19 22:25:08.455609 -0700 PDT m=+2.287351787 // // This contains the most recently Markdown source for the Statping Wiki. package source // CompiledWiki contains all of the Statping Wiki pages from the Github Wiki repo. -var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintence\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") +var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") diff --git a/version.txt b/version.txt index e1259258..f1a296d9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.80.63 +0.80.64 From 15e3b83f9b62566d82aeadd92a156c2e8fefbc90 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Thu, 19 Sep 2019 22:45:12 -0700 Subject: [PATCH 014/100] SMTP email notifier OnTest() fix --- notifiers/email.go | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/notifiers/email.go b/notifiers/email.go index 1c06dd50..dd08e355 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -24,7 +24,6 @@ import ( "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "html/template" - "net/smtp" "time" ) @@ -225,24 +224,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 +242,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.dialSend(email) - return err + return u.dialSend(email) } func (u *email) dialSend(email *emailOutgoing) error { From 6ed523b12d5833c63f7887a219da9c471bcbe896 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 20 Sep 2019 00:00:25 -0700 Subject: [PATCH 015/100] travis-ci golang 1.13 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ab3d8284..ff0bff14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ os: - linux language: go -go: 1.12.1 +go: 1.13.0 go_import_path: github.com/hunterlong/statping cache: From 23a9e8f93d6e9aa9c65bc45e3ed7d61f4e6b78dd Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 20 Sep 2019 01:05:35 -0700 Subject: [PATCH 016/100] travis-ci golang 1.13 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ff0bff14..bbf9244d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ os: - linux language: go -go: 1.13.0 +go: 1.13 go_import_path: github.com/hunterlong/statping cache: From 206a90cc089c13b4c29512a23d46d048bb5e7098 Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Sun, 22 Sep 2019 16:36:40 +0300 Subject: [PATCH 017/100] fix timeout value --- utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 2d7fcd94..4fdee729 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -301,8 +301,8 @@ func HttpRequest(url, method string, content interface{}, headers []string, body var resp *http.Response dialer := &net.Dialer{ - Timeout: timeout * time.Second, - KeepAlive: timeout * time.Second, + Timeout: timeout, + KeepAlive: timeout, } transport := &http.Transport{ From 2824c8fe9ab99fda61f0c347cd688c19e6208ade Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Mon, 23 Sep 2019 12:50:38 -0700 Subject: [PATCH 018/100] travis-ci build fix --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1b4bed6f..05a1e1e8 100644 --- a/Makefile +++ b/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: From f47e611e27dfa340fd5a238bc2471548b185c579 Mon Sep 17 00:00:00 2001 From: Tufan Baris Yildirim Date: Fri, 27 Sep 2019 11:35:07 +0300 Subject: [PATCH 019/100] add verifyssl field to csv import handler. --- handlers/settings.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/handlers/settings.go b/handlers/settings.go index 89b5839b..c938c2b2 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -144,7 +144,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 +169,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 +190,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 From 4ec3a46732a9419e1159b89f5d0c5043423f4026 Mon Sep 17 00:00:00 2001 From: Jimmy Casey Date: Mon, 30 Sep 2019 17:13:34 +0100 Subject: [PATCH 020/100] Fixed incorrect service status on prompt. Boolean was being evaluated as string. --- source/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/js/main.js b/source/js/main.js index 3ddf566d..ee0a1db1 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -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", From b1bcc67833db2060f6b8d6e9765ce359cd7cbd24 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 14:18:57 +0200 Subject: [PATCH 021/100] Add new Field to Core Struct Add Field `UpdateNotify` to `Core` Struct. This Field shows if the User want that only Updates are send, or every Status Message. --- types/core.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types/core.go b/types/core.go index 03756cc2..ca73c684 100644 --- a/types/core.go +++ b/types/core.go @@ -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"` From e73993cef2a0d70d04c45e151c81695bece615cc Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 14:22:20 +0200 Subject: [PATCH 022/100] Add Toggle-Switch to settings HTML Template --- source/tmpl/settings.gohtml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/tmpl/settings.gohtml b/source/tmpl/settings.gohtml index 39cd66b1..05324939 100644 --- a/source/tmpl/settings.gohtml +++ b/source/tmpl/settings.gohtml @@ -37,6 +37,20 @@
+
+
+ + + + + + + + + +
+
+
From 29ace420ae90f71b5bbf1d8b12d753ab15ba1c42 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 14:22:35 +0200 Subject: [PATCH 023/100] Add `UpdateNotify` Type to saveSettingsHandler() --- handlers/settings.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/handlers/settings.go b/handlers/settings.go index 89b5839b..93976c84 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -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") } From 4227f3c88f394fe9bc5342b4415f8f3f99c0d1f4 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 14:23:07 +0200 Subject: [PATCH 024/100] Add `UPDATENOTIFY` Handler Function --- handlers/function.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/handlers/function.go b/handlers/function.go index d17f1452..7ac44649 100644 --- a/handlers/function.go +++ b/handlers/function.go @@ -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) }, From d062839b52a721a7c7a28a4aabe25ebc765f5d98 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 14:47:56 +0200 Subject: [PATCH 025/100] Add Field `UserNotified` to Service Struct The Field `UserNotified` is to indicate if a User has already been notified about the Downtime of a Service. --- types/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types/service.go b/types/service.go index f0435dba..f607064e 100644 --- a/types/service.go +++ b/types/service.go @@ -50,6 +50,7 @@ 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 LastStatusCode int `gorm:"-" json:"status_code"` LastOnline time.Time `gorm:"-" json:"last_success"` Failures []FailureInterface `gorm:"-" json:"failures,omitempty"` From 2a3c68201340da0920b775500a4f0fa22396ff3d Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 14:48:56 +0200 Subject: [PATCH 026/100] Add implementation for `UpdateNotified` Check --- core/notifier/events.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/notifier/events.go b/core/notifier/events.go index de9eb1b5..2fd45b61 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" + "github.com/hunterlong/statping/core" ) // OnSave will trigger a notifier when it has been saved - Notifier interface @@ -38,6 +39,19 @@ func OnFailure(s *types.Service, f *types.Failure) { if !s.AllowNotifications.Bool { return } + + // check if User wants to receive every Status Change + if core.CoreApp.UpdateNotify.Bool { + // 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) && inLimits(comm) { notifier := comm.(Notifier).Select() @@ -45,7 +59,6 @@ func OnFailure(s *types.Service, f *types.Failure) { comm.(BasicEvents).OnFailure(s, f) } } - } // OnSuccess will be triggered when a service is successful - BasicEvents interface @@ -53,6 +66,12 @@ func OnSuccess(s *types.Service) { if !s.AllowNotifications.Bool { return } + + // check if User wants to receive every Status Change + if core.CoreApp.UpdateNotify.Bool && s.UserNotified { + s.UserNotified = false + } + for _, comm := range AllCommunications { if isType(comm, new(BasicEvents)) && isEnabled(comm) && inLimits(comm) { notifier := comm.(Notifier).Select() @@ -60,7 +79,6 @@ func OnSuccess(s *types.Service) { comm.(BasicEvents).OnSuccess(s) } } - } // OnNewService is triggered when a new service is created - ServiceEvents interface From c9f140f2dcef85be130bba3f555610c73b1e87fb Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 15:29:26 +0200 Subject: [PATCH 027/100] Update Online Message for Telegram Notifier --- notifiers/telegram.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/notifiers/telegram.go b/notifiers/telegram.go index 000b3292..c8497bb6 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/url" @@ -99,7 +100,12 @@ func (u *telegram) OnFailure(s *types.Service, f *types.Failure) { func (u *telegram) OnSuccess(s *types.Service) { if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) + var msg interface{} + if core.CoreApp.UpdateNotify.Bool { + msg = core.ReturnService(s).SmallText() + } else { + msg = fmt.Sprintf("Your service '%v' is currently offline!", s.Name) + } u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } } From df3eb914ed840bc8f3a5f7f8936f6ab5826f0f0b Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 15:36:45 +0200 Subject: [PATCH 028/100] Add Email, Mobile and Discord Online Messages --- notifiers/discord.go | 9 ++++++++- notifiers/email.go | 10 +++++++++- notifiers/mobile.go | 10 +++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/notifiers/discord.go b/notifiers/discord.go index 82cb1c70..3bc3730f 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "strings" @@ -77,7 +78,13 @@ func (u *discord) OnFailure(s *types.Service, f *types.Failure) { func (u *discord) OnSuccess(s *types.Service) { if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name) + var msg interface{} + if core.CoreApp.UpdateNotify.Bool { + msg = fmt.Sprintf(`{"content": "%s"}`, core.ReturnService(s).SmallText()) + } else { + msg = fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name) + } + u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } } diff --git a/notifiers/email.go b/notifiers/email.go index dd08e355..fc5f6674 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -20,6 +20,7 @@ import ( "crypto/tls" "fmt" "github.com/go-mail/mail" + "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core/notifier" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" @@ -199,10 +200,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 { + var msg string + if core.CoreApp.UpdateNotify.Bool { + msg = core.ReturnService(s).SmallText() + } else { + msg = fmt.Sprintf("Service %v is Back Online", s.Name) + } + 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, diff --git a/notifiers/mobile.go b/notifiers/mobile.go index 3cceebf1..4bc146a3 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -19,6 +19,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core/notifier" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" @@ -106,9 +107,16 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) { func (u *mobilePush) OnSuccess(s *types.Service) { data := dataJson(s, nil) if !s.Online { + var msgStr string + if core.CoreApp.UpdateNotify.Bool { + msgStr = core.ReturnService(s).SmallText() + } else { + msgStr = fmt.Sprintf("Your Service %v is Back Online", s.Name) + } + 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, From a9b559262ab16ff9d03526f32797eb2d6cdb298e Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 15:38:40 +0200 Subject: [PATCH 029/100] Update Online Message for Line-Notify Notifier --- notifiers/line_notify.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index 4e5cd3f8..131d2f4c 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -18,6 +18,7 @@ package notifiers import ( "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/url" @@ -79,8 +80,14 @@ 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 { + var msg string + if core.CoreApp.UpdateNotify.Bool { + msg = core.ReturnService(s).SmallText() + } else { + msg = fmt.Sprintf("Your Service %v is Back Online", s.Name) + } + 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) } } From 786bfe75fc19b1b413adda7090f7e4bfab9a5b6b Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 15:40:34 +0200 Subject: [PATCH 030/100] Update Online Message for Twilio Notifier --- notifiers/twilio.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 6dcf9fbd..82a82838 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/url" @@ -109,7 +110,12 @@ func (u *twilio) OnFailure(s *types.Service, f *types.Failure) { func (u *twilio) OnSuccess(s *types.Service) { if !s.Online { u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - msg := fmt.Sprintf("Your service '%v' is back online!", s.Name) + var msg string + if core.CoreApp.UpdateNotify.Bool { + msg = core.ReturnService(s).SmallText() + } else { + msg = fmt.Sprintf("Your Service %v is Back Online", s.Name) + } u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } } From d81c525d71b60ae0fa685fa0be403f4ab4fa85f8 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 17:18:33 +0200 Subject: [PATCH 031/100] Add new Fields to `Service` Struct Add `UpdateNotify` Field to allow the Notifiers to check if `UpdateNotify` has been enabled in `CoreApp`. Add `DownText` Field to have a default Downtime Text which also has the 'Service has been down for n-Seconds' Text. Add `SuccessNotified` Field to check if a "Success" Message has already been send. This is because the `s.Online` Field is always "false" if the OnSuccess() Function is called of an Notifier. --- types/service.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/types/service.go b/types/service.go index f607064e..a904f980 100644 --- a/types/service.go +++ b/types/service.go @@ -50,7 +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 + 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"` From e1c41966e4d81b31f4034be1196008e1cdd9f4a1 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 17:23:20 +0200 Subject: [PATCH 032/100] Add implementation of new `Service` Fields in checker.go Add Variable Value changes of `SuccessNOtified`, `UpdateNotify` and `DownText` to checker.go. So the Values can be used in other packages. --- core/checker.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/checker.go b/core/checker.go index 6eff907e..2f837f80 100644 --- a/core/checker.go +++ b/core/checker.go @@ -263,6 +263,7 @@ func recordSuccess(s *Service) { s.CreateHit(hit) s.Online = true notifier.OnSuccess(s.Service) + 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) } From 2534078e116fd0a1c9533bcbdf467da62a385ea6 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 17:24:28 +0200 Subject: [PATCH 033/100] Switch to new Service-Fields in events.go --- core/notifier/events.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/notifier/events.go b/core/notifier/events.go index 2fd45b61..a6a93246 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -19,7 +19,6 @@ import ( "fmt" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" - "github.com/hunterlong/statping/core" ) // OnSave will trigger a notifier when it has been saved - Notifier interface @@ -41,7 +40,7 @@ func OnFailure(s *types.Service, f *types.Failure) { } // check if User wants to receive every Status Change - if core.CoreApp.UpdateNotify.Bool { + if s.UpdateNotify { // send only if User hasn't been already notified about the Downtime if !s.UserNotified { s.UserNotified = true @@ -68,7 +67,7 @@ func OnSuccess(s *types.Service) { } // check if User wants to receive every Status Change - if core.CoreApp.UpdateNotify.Bool && s.UserNotified { + if s.UpdateNotify && s.UserNotified { s.UserNotified = false } From 6ba5d53a504685bce3dd7278b225b66cf9e47406 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 17:25:03 +0200 Subject: [PATCH 034/100] Remove `/core` include from notifier Package Since we have special Fields for that prupose we can remove the `core` Package include. --- notifiers/discord.go | 1 - notifiers/email.go | 1 - notifiers/line_notify.go | 1 - notifiers/mobile.go | 1 - notifiers/telegram.go | 1 - notifiers/twilio.go | 1 - 6 files changed, 6 deletions(-) diff --git a/notifiers/discord.go b/notifiers/discord.go index 3bc3730f..7fd6beeb 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" - "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "strings" diff --git a/notifiers/email.go b/notifiers/email.go index fc5f6674..9d8a65d9 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -20,7 +20,6 @@ import ( "crypto/tls" "fmt" "github.com/go-mail/mail" - "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core/notifier" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index 131d2f4c..5f0c5bf4 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -18,7 +18,6 @@ package notifiers import ( "fmt" "github.com/hunterlong/statping/core/notifier" - "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/url" diff --git a/notifiers/mobile.go b/notifiers/mobile.go index 4bc146a3..d91eb121 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -19,7 +19,6 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core/notifier" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" diff --git a/notifiers/telegram.go b/notifiers/telegram.go index c8497bb6..692843e0 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" - "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/url" diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 82a82838..12e1c8ca 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" - "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/url" From d462e54053db4c1e7d43811755abd010b266d557 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 4 Oct 2019 17:25:57 +0200 Subject: [PATCH 035/100] Replace all `core.*` Code Lines from notifier Package Replace all `core.*` Code Lines from notifier Package with the new Fields of the Service-Struct. --- notifiers/discord.go | 9 ++++----- notifiers/email.go | 9 ++++----- notifiers/line_notify.go | 9 ++++----- notifiers/mobile.go | 9 ++++----- notifiers/telegram.go | 10 +++++----- notifiers/twilio.go | 10 +++++----- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/notifiers/discord.go b/notifiers/discord.go index 7fd6beeb..2d9c345b 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -75,14 +75,13 @@ 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)) var msg interface{} - if core.CoreApp.UpdateNotify.Bool { - msg = fmt.Sprintf(`{"content": "%s"}`, core.ReturnService(s).SmallText()) - } else { - msg = fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name) + if s.UpdateNotify { + s.UpdateNotify = false } + msg = s.DownText u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } diff --git a/notifiers/email.go b/notifiers/email.go index 9d8a65d9..d57beee1 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -198,13 +198,12 @@ 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 core.CoreApp.UpdateNotify.Bool { - msg = core.ReturnService(s).SmallText() - } else { - msg = fmt.Sprintf("Service %v is Back Online", s.Name) + if s.UpdateNotify { + s.UpdateNotify = false } + msg = s.DownText u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) email := &emailOutgoing{ diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index 5f0c5bf4..069de7ef 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -78,13 +78,12 @@ 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 core.CoreApp.UpdateNotify.Bool { - msg = core.ReturnService(s).SmallText() - } else { - msg = fmt.Sprintf("Your Service %v is Back Online", s.Name) + if s.UpdateNotify { + s.UpdateNotify = false } + msg = s.DownText u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) diff --git a/notifiers/mobile.go b/notifiers/mobile.go index d91eb121..ed238580 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -105,13 +105,12 @@ 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 core.CoreApp.UpdateNotify.Bool { - msgStr = core.ReturnService(s).SmallText() - } else { - msgStr = fmt.Sprintf("Your Service %v is Back Online", s.Name) + if s.UpdateNotify { + s.UpdateNotify = false } + msgStr = s.DownText u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) msg := &pushArray{ diff --git a/notifiers/telegram.go b/notifiers/telegram.go index 692843e0..6215cf43 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -97,14 +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)) var msg interface{} - if core.CoreApp.UpdateNotify.Bool { - msg = core.ReturnService(s).SmallText() - } else { - msg = fmt.Sprintf("Your service '%v' is currently offline!", s.Name) + if s.UpdateNotify { + s.UpdateNotify = false } + msg = s.DownText + u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } } diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 12e1c8ca..e660173d 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -107,14 +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)) var msg string - if core.CoreApp.UpdateNotify.Bool { - msg = core.ReturnService(s).SmallText() - } else { - msg = fmt.Sprintf("Your Service %v is Back Online", s.Name) + if s.UpdateNotify { + s.UpdateNotify = false } + msg = s.DownText + u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) } } From ca2a7e69ba06919a90b31055fd8251c19ce11984 Mon Sep 17 00:00:00 2001 From: Kanin Peanviriyakulkit Date: Wed, 9 Oct 2019 15:46:40 +0700 Subject: [PATCH 036/100] Update line_notify.go I think we should send notify when update the Access token. --- notifiers/line_notify.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index 4e5cd3f8..8ab5ab6c 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -87,7 +87,8 @@ func (u *lineNotifier) OnSuccess(s *types.Service) { // 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 } From c6da1ad6830959c1021874cdc2c5cfd11e71a202 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Tue, 15 Oct 2019 08:58:33 -0700 Subject: [PATCH 037/100] Update version.txt --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f1a296d9..a0c4959c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.80.64 +0.80.65 From 2fd1332906cf9bb67e49384fef70bb8c925a56fd Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 24 Oct 2019 16:17:05 -0700 Subject: [PATCH 038/100] minor fix - package updates --- .gitattributes | 2 ++ Gopkg.lock | 39 +++++++++++++++++++-------------------- Gopkg.toml | 6 +++--- notifiers/line_notify.go | 2 +- source/wiki.go | 4 ++-- 5 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b552774f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.go -text diff=golang +*.gohtml -text diff=golang diff --git a/Gopkg.lock b/Gopkg.lock index d45192e8..db826539 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,7 +2,7 @@ [[projects]] - digest = "1:d4bfd57449b0bdfe927ec45c8463afd8f5b6012d4bcd5a9da8581a408c23e57c" + digest = "1:ecb63d398130fee54aec9b9aa80df16b72c9d785255735bd5a30aa8889758a37" name = "github.com/99designs/gqlgen" packages = [ "complexity", @@ -11,8 +11,8 @@ "handler", ] pruneopts = "UT" - revision = "a7bc468ca1b184a5ce1b07ea331e0121fc56ae82" - version = "v0.9.3" + revision = "efb6efe06c6e4fc706440acebf6f81fff85f295c" + version = "v0.10.1" [[projects]] digest = "1:07f7314344b2771963ada0b2a4a426c59d782dac227dcfff2499188a186446c0" @@ -74,12 +74,12 @@ version = "v1.4.1" [[projects]] - digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" name = "github.com/go-yaml/yaml" packages = ["."] pruneopts = "UT" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - version = "v2.2.2" + revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" + version = "v2.2.4" [[projects]] digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296" @@ -125,7 +125,7 @@ version = "v0.5.3" [[projects]] - digest = "1:b0c1770be8c52cf00117b98049de1e4df91c8df588102198364b09669bb60178" + digest = "1:dbdfa0171086e696a3e72527a13b02e653b7e03cd168618f505bd71bc6a3257b" name = "github.com/jinzhu/gorm" packages = [ ".", @@ -134,8 +134,8 @@ "dialects/sqlite", ] pruneopts = "UT" - revision = "836fb2c19d84dac7b0272958dfb9af7cf0d0ade4" - version = "v1.9.10" + revision = "81c17a7e2529c59efc4e74c5b32c1fb71fb12fa2" + version = "v1.9.11" [[projects]] digest = "1:01ed62f8f4f574d8aff1d88caee113700a2b44c42351943fa73cc1808f736a50" @@ -239,11 +239,11 @@ "blowfish", ] pruneopts = "UT" - revision = "9756ffdc24725223350eb3266ffb92590d28f278" + revision = "87dc89f01550277dc22b74ffcf4cd89fa2f40f4c" [[projects]] branch = "master" - digest = "1:caffb9a4f8c756941de4b3eb577abd167e7fd4b570f2078c05ceb8835a1514cb" + digest = "1:b1c9d783fea9a0529d1aff7f1e3ffe9bfa474ad65a273d78c98d43e77c1fdd31" name = "golang.org/x/net" packages = [ "bpf", @@ -254,26 +254,26 @@ "ipv6", ] pruneopts = "UT" - revision = "ba9fcec4b297b415637633c5a6e8fa592e4a16c3" + revision = "da9a3fd4c5820e74b24a6cb7fb438dc9b0dd377c" [[projects]] branch = "master" - digest = "1:d94059c196c160bd1c4030d49ffaa39a456be516501e5916bea663f5d79a75ec" + digest = "1:9f5be1dbb5091bb62d83fbb6db8e794adb8f0e05424679d6f15a17b670e38b4c" name = "golang.org/x/sys" packages = [ "unix", "windows", ] pruneopts = "UT" - revision = "9109b7679e13aa34a54834cfb4949cac4b96e576" + revision = "b09406accb4736d857a32bf9444cd7edae2ffa79" [[projects]] digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" name = "google.golang.org/appengine" packages = ["cloudsql"] pruneopts = "UT" - revision = "5f2a59506353b8d5ba8cbbcd9f3c1f41f1eaf079" - version = "v1.6.2" + revision = "971852bfffca25b069c31162ae8f247a3dba083b" + version = "v1.6.5" [[projects]] branch = "v3" @@ -300,12 +300,12 @@ version = "v2.0.1" [[projects]] - digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" name = "gopkg.in/yaml.v2" packages = ["."] pruneopts = "UT" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - version = "v2.2.2" + revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" + version = "v2.2.4" [solve-meta] analyzer-name = "dep" @@ -315,7 +315,6 @@ "github.com/99designs/gqlgen/graphql/introspection", "github.com/99designs/gqlgen/handler", "github.com/GeertJohan/go.rice", - "github.com/GeertJohan/go.rice/embedded", "github.com/ararog/timeago", "github.com/go-mail/mail", "github.com/go-yaml/yaml", diff --git a/Gopkg.toml b/Gopkg.toml index 402837b4..983ecf5f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,7 +27,7 @@ [[constraint]] name = "github.com/99designs/gqlgen" - version = "0.9.3" + version = "0.10.1" [[constraint]] name = "github.com/GeertJohan/go.rice" @@ -43,7 +43,7 @@ [[constraint]] name = "github.com/go-yaml/yaml" - version = "2.2.2" + version = "2.2.4" [[constraint]] name = "github.com/gorilla/mux" @@ -55,7 +55,7 @@ [[constraint]] name = "github.com/jinzhu/gorm" - version = "1.9.10" + version = "1.9.11" [[constraint]] name = "github.com/joho/godotenv" diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index 7409f249..eb6084ee 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -94,6 +94,6 @@ func (u *lineNotifier) OnSuccess(s *types.Service) { func (u *lineNotifier) OnSave() error { msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method) utils.Log(1, msg) - u.AddQueue("saved", message) + u.AddQueue("saved", msg) return nil } diff --git a/source/wiki.go b/source/wiki.go index ae38364c..c74283d8 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,9 +1,9 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-09-19 22:25:08.455609 -0700 PDT m=+2.287351787 +// 2019-10-24 16:08:58.145719 -0700 PDT m=+0.562621923 // // This contains the most recently Markdown source for the Statping Wiki. package source // CompiledWiki contains all of the Statping Wiki pages from the Github Wiki repo. -var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSL=false # enable ssl_mode for postgres (true/false)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") +var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Upgrading Staping\nYou can upgrade the Statping executable by running the commands below on your EC2 instance.\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-x64.tar.gz\ntar -xvzf statping-linux-x64.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n```\nYou can test the version number by running `statping version`.\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSLMODE=false # enable ssl_mode for postgres (To enable use require)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") From 8aa2dba41cc802c0feebaf3d92b56911baf409ff Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 25 Oct 2019 08:13:32 -0700 Subject: [PATCH 039/100] update --- cmd/main.go | 9 ---- core/core.go | 19 ++++++- core/notifier/audit.go | 2 +- core/notifier/events.go | 4 +- core/notifier/example_test.go | 7 +-- core/notifier/notifiers.go | 38 ++++++-------- core/notifier/notifiers_test.go | 5 -- notifiers/command.go | 20 +++----- notifiers/command_test.go | 88 ++++++++++++++++----------------- notifiers/discord.go | 18 ++----- notifiers/discord_test.go | 42 +++++++--------- notifiers/email.go | 11 +---- notifiers/email_test.go | 66 ++++++++++++------------- notifiers/line_notify.go | 10 +--- notifiers/mobile.go | 18 ++----- notifiers/mobile_test.go | 83 ++++++++++++++----------------- notifiers/slack.go | 13 ++--- notifiers/slack_test.go | 56 ++++++++++----------- notifiers/telegram.go | 10 +--- notifiers/telegram_test.go | 46 ++++++++--------- notifiers/twilio.go | 10 +--- notifiers/twilio_test.go | 46 ++++++++--------- notifiers/webhook.go | 14 ++---- notifiers/webhook_test.go | 56 +++++++++------------ source/wiki.go | 2 +- 25 files changed, 289 insertions(+), 404 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 8c00de1a..b0990e07 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,7 +20,6 @@ import ( "fmt" "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/handlers" - _ "github.com/hunterlong/statping/notifiers" "github.com/hunterlong/statping/plugin" "github.com/hunterlong/statping/source" "github.com/hunterlong/statping/utils" @@ -119,11 +118,3 @@ func mainProcess() { os.Exit(1) } } - -func ForEachPlugin() { - if len(core.CoreApp.Plugins) > 0 { - //for _, p := range core.Plugins { - // p.OnShutdown() - //} - } -} diff --git a/core/core.go b/core/core.go index 2253ee4a..4ab3f16c 100644 --- a/core/core.go +++ b/core/core.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/notifiers" "github.com/hunterlong/statping/source" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" @@ -64,7 +65,8 @@ func InitApp() { InsertNotifierDB() CoreApp.SelectAllServices(true) checkServices() - CoreApp.Notifications = notifier.Load() + AttachNotifiers() + CoreApp.Notifications = notifier.AllCommunications go DatabaseMaintence() } @@ -177,6 +179,21 @@ func GetLocalIP() string { return "http://localhost" } +// AttachNotifiers will attach all the notifier's into the system +func AttachNotifiers() error { + return notifier.AddNotifiers( + notifiers.Command, + notifiers.Discorder, + notifiers.Emailer, + notifiers.LineNotify, + notifiers.Mobile, + notifiers.Slacker, + notifiers.Telegram, + notifiers.Twilio, + notifiers.Webhook, + ) +} + // ServiceOrder will reorder the services based on 'order_id' (Order) type ServiceOrder []types.ServiceInterface diff --git a/core/notifier/audit.go b/core/notifier/audit.go index 512f3ad5..f1255cc4 100644 --- a/core/notifier/audit.go +++ b/core/notifier/audit.go @@ -25,7 +25,7 @@ var ( ) func checkNotifierForm(n Notifier) error { - notifier := asNotification(n) + notifier := n.Select() for _, f := range notifier.Form { contains := contains(f.DbField, allowedVars) if !contains { diff --git a/core/notifier/events.go b/core/notifier/events.go index fc55d1b4..34900aa4 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -54,7 +54,7 @@ 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)) + utils.Log(1, fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name)) comm.(BasicEvents).OnFailure(s, f) } } @@ -74,7 +74,7 @@ func OnSuccess(s *types.Service) { 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)) + utils.Log(1, fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name)) comm.(BasicEvents).OnSuccess(s) } } diff --git a/core/notifier/example_test.go b/core/notifier/example_test.go index 1d46b1e8..d2c45aba 100644 --- a/core/notifier/example_test.go +++ b/core/notifier/example_test.go @@ -18,6 +18,7 @@ package notifier import ( "errors" "fmt" + c "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/types" "time" ) @@ -83,7 +84,7 @@ var example = &ExampleNotifier{&Notification{ // init will be ran when Statping is loaded, AddNotifier will add the notifier instance to the system func init() { - AddNotifier(example) + c.AttachNotifiers() } // Send is the main function to hold your notifier functionality @@ -209,14 +210,14 @@ func ExampleNotification() { }} // AddNotifier accepts a Notifier to load into the Statping Notification system - err := AddNotifier(example) + err := AddNotifiers(example) fmt.Println(err) // Output: } // Add a Notifier to the AddQueue function to insert it into the system func ExampleAddNotifier() { - err := AddNotifier(example) + err := AddNotifiers(example) fmt.Println(err) // Output: } diff --git a/core/notifier/notifiers.go b/core/notifier/notifiers.go index f0924fa2..7d97d4dd 100644 --- a/core/notifier/notifiers.go +++ b/core/notifier/notifiers.go @@ -102,6 +102,7 @@ func (n *Notification) AfterFind() (err error) { func (n *Notification) AddQueue(uid string, msg interface{}) { data := &QueueData{uid, msg} n.Queue = append(n.Queue, data) + utils.Log(0, fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) } // CanTest returns true if the notifier implements the OnTest interface @@ -126,29 +127,21 @@ func asNotification(n Notifier) *Notification { } // AddNotifier accept a Notifier interface to be added into the array -func AddNotifier(n Notifier) error { - if isType(n, new(Notifier)) { - err := checkNotifierForm(n) - if err != nil { - return err +func AddNotifiers(notifiers ...Notifier) error { + for _, n := range notifiers { + if isType(n, new(Notifier)) { + err := checkNotifierForm(n) + if err != nil { + return err + } + AllCommunications = append(AllCommunications, n) + Init(n) + } else { + return errors.New("notifier does not have the required methods") } - AllCommunications = append(AllCommunications, n) - } else { - return errors.New("notifier does not have the required methods") - } - return nil -} - -// Load is called by core to add all the notifier into memory -func Load() []types.AllNotifiers { - var notifiers []types.AllNotifiers - for _, comm := range AllCommunications { - n := comm.(Notifier) - Init(n) - notifiers = append(notifiers, n) } startAllNotifiers() - return notifiers + return nil } // normalizeType will accept multiple interfaces and converts it into a string for logging @@ -179,7 +172,6 @@ func (n *Notification) makeLog(msg interface{}) { Time: utils.Timestamp(time.Now()), Timestamp: time.Now(), } - utils.Log(1, fmt.Sprintf("Notifier %v has sent a message %v", n.Method, log.Message)) n.logs = append(n.logs, log) } @@ -297,7 +289,9 @@ CheckNotifier: msg := notification.Queue[0] err := n.Send(msg.Data) if err != nil { - utils.Log(2, fmt.Sprintf("notifier %v had an error: %v", notification.Method, err)) + utils.Log(2, fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err)) + } else { + utils.Log(1, fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue))) } notification.makeLog(msg.Data) if len(notification.Queue) > 1 { diff --git a/core/notifier/notifiers_test.go b/core/notifier/notifiers_test.go index c8686395..b7fd78e4 100644 --- a/core/notifier/notifiers_test.go +++ b/core/notifier/notifiers_test.go @@ -79,11 +79,6 @@ func TestIsBasicType(t *testing.T) { assert.True(t, isType(example, new(Tester))) } -func TestLoad(t *testing.T) { - notifiers := Load() - assert.Equal(t, 1, len(notifiers)) -} - func TestIsInDatabase(t *testing.T) { in := isInDatabase(example.Notification) assert.True(t, in) diff --git a/notifiers/command.go b/notifiers/command.go index 6216e794..2a1fe232 100644 --- a/notifiers/command.go +++ b/notifiers/command.go @@ -27,10 +27,10 @@ type commandLine struct { *notifier.Notification } -var command = &commandLine{¬ifier.Notification{ - Method: "command", +var Command = &commandLine{¬ifier.Notification{ + Method: "Command", Title: "Shell Command", - Description: "Shell Command allows you to run a customized shell/bash command on the local machine it's running on.", + Description: "Shell Command allows you to run a customized shell/bash Command on the local machine it's running on.", Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(1 * time.Second), @@ -47,24 +47,16 @@ var command = &commandLine{¬ifier.Notification{ Title: "Command to Run on OnSuccess", Placeholder: "curl google.com", DbField: "var1", - SmallText: "This command will run every time a service is receiving a Successful event.", + SmallText: "This Command will run every time a service is receiving a Successful event.", }, { Type: "text", Title: "Command to Run on OnFailure", Placeholder: "curl offline.com", DbField: "var2", - SmallText: "This command will run every time a service is receiving a Failing event.", + SmallText: "This Command will run every time a service is receiving a Failing event.", }}}, } -// init the command notifier -func init() { - err := notifier.AddNotifier(command) - if err != nil { - panic(err) - } -} - func runCommand(app, cmd string) (string, string, error) { outStr, errStr, err := utils.Command(cmd) return outStr, errStr, err @@ -102,7 +94,7 @@ func (u *commandLine) OnTest() error { return err } -// Send for commandLine will send message to expo command push notifications endpoint +// Send for commandLine will send message to expo Command push notifications endpoint func (u *commandLine) Send(msg interface{}) error { cmd := msg.(string) _, _, err := runCommand(u.Host, cmd) diff --git a/notifiers/command_test.go b/notifiers/command_test.go index f6ed7314..36ad0aa5 100644 --- a/notifiers/command_test.go +++ b/notifiers/command_test.go @@ -28,79 +28,75 @@ const ( func TestCommandNotifier(t *testing.T) { t.Parallel() - command.Host = "sh" - command.Var1 = commandTest - command.Var2 = commandTest + Command.Host = "sh" + Command.Var1 = commandTest + Command.Var2 = commandTest currentCount = CountNotifiers() - t.Run("Load command", func(t *testing.T) { - command.Host = "sh" - command.Var1 = commandTest - command.Var2 = commandTest - command.Delay = time.Duration(100 * time.Millisecond) - command.Limits = 99 - err := notifier.AddNotifier(command) + t.Run("Load Command", func(t *testing.T) { + Command.Host = "sh" + Command.Var1 = commandTest + Command.Var2 = commandTest + Command.Delay = time.Duration(100 * time.Millisecond) + Command.Limits = 99 + err := notifier.AddNotifiers(Command) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", command.Author) - assert.Equal(t, "sh", command.Host) - assert.Equal(t, commandTest, command.Var1) - assert.Equal(t, commandTest, command.Var2) + assert.Equal(t, "Hunter Long", Command.Author) + assert.Equal(t, "sh", Command.Host) + assert.Equal(t, commandTest, Command.Var1) + assert.Equal(t, commandTest, Command.Var2) }) - t.Run("Load command Notifier", func(t *testing.T) { - notifier.Load() + t.Run("Command Notifier Tester", func(t *testing.T) { + assert.True(t, Command.CanTest()) }) - t.Run("command Notifier Tester", func(t *testing.T) { - assert.True(t, command.CanTest()) - }) - - t.Run("command Within Limits", func(t *testing.T) { - ok, err := command.WithinLimits() + t.Run("Command Within Limits", func(t *testing.T) { + ok, err := Command.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) - t.Run("command OnFailure", func(t *testing.T) { - command.OnFailure(TestService, TestFailure) - assert.Equal(t, 1, len(command.Queue)) + t.Run("Command OnFailure", func(t *testing.T) { + Command.OnFailure(TestService, TestFailure) + assert.Equal(t, 1, len(Command.Queue)) }) - t.Run("command OnSuccess", func(t *testing.T) { - command.OnSuccess(TestService) - assert.Equal(t, 1, len(command.Queue)) + t.Run("Command OnSuccess", func(t *testing.T) { + Command.OnSuccess(TestService) + assert.Equal(t, 1, len(Command.Queue)) }) - t.Run("command OnSuccess Again", func(t *testing.T) { - command.OnSuccess(TestService) - assert.Equal(t, 1, len(command.Queue)) - go notifier.Queue(command) + t.Run("Command OnSuccess Again", func(t *testing.T) { + Command.OnSuccess(TestService) + assert.Equal(t, 1, len(Command.Queue)) + go notifier.Queue(Command) time.Sleep(20 * time.Second) - assert.Equal(t, 0, len(command.Queue)) + assert.Equal(t, 0, len(Command.Queue)) }) - t.Run("command Within Limits again", func(t *testing.T) { - ok, err := command.WithinLimits() + t.Run("Command Within Limits again", func(t *testing.T) { + ok, err := Command.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) - t.Run("command Send", func(t *testing.T) { - command.Send(commandTest) - assert.Equal(t, 0, len(command.Queue)) + t.Run("Command Send", func(t *testing.T) { + Command.Send(commandTest) + assert.Equal(t, 0, len(Command.Queue)) }) - t.Run("command Test", func(t *testing.T) { - command.OnTest() + t.Run("Command Test", func(t *testing.T) { + Command.OnTest() }) - t.Run("command Queue", func(t *testing.T) { - go notifier.Queue(command) + t.Run("Command Queue", func(t *testing.T) { + go notifier.Queue(Command) time.Sleep(5 * time.Second) - assert.Equal(t, "sh", command.Host) - assert.Equal(t, commandTest, command.Var1) - assert.Equal(t, commandTest, command.Var2) - assert.Equal(t, 0, len(command.Queue)) + assert.Equal(t, "sh", Command.Host) + assert.Equal(t, commandTest, Command.Var1) + assert.Equal(t, commandTest, Command.Var2) + assert.Equal(t, 0, len(Command.Queue)) }) } diff --git a/notifiers/discord.go b/notifiers/discord.go index 2d9c345b..d6a37d0a 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -31,10 +31,10 @@ type discord struct { *notifier.Notification } -var discorder = &discord{¬ifier.Notification{ +var Discorder = &discord{¬ifier.Notification{ Method: "discord", Title: "discord", - Description: "Send notifications to your discord channel using discord webhooks. Insert your discord channel webhook URL to receive notifications. Based on the discord webhooker API.", + Description: "Send notifications to your discord channel using discord webhooks. Insert your discord channel Webhook URL to receive notifications. Based on the discord webhooker API.", Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(5 * time.Second), @@ -43,23 +43,15 @@ var discorder = &discord{¬ifier.Notification{ Form: []notifier.NotificationForm{{ Type: "text", Title: "discord webhooker URL", - Placeholder: "Insert your webhook URL here", + Placeholder: "Insert your Webhook URL here", DbField: "host", }}}, } -// init the discord notifier -func init() { - err := notifier.AddNotifier(discorder) - if err != nil { - panic(err) - } -} - // 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), true) + _, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true) return err } @@ -98,7 +90,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), true) + 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 } diff --git a/notifiers/discord_test.go b/notifiers/discord_test.go index 02e4af9d..faa01336 100644 --- a/notifiers/discord_test.go +++ b/notifiers/discord_test.go @@ -30,7 +30,7 @@ var ( func init() { DISCORD_URL = os.Getenv("DISCORD_URL") - discorder.Host = DISCORD_URL + Discorder.Host = DISCORD_URL } func TestDiscordNotifier(t *testing.T) { @@ -42,36 +42,32 @@ func TestDiscordNotifier(t *testing.T) { currentCount = CountNotifiers() t.Run("Load discord", func(t *testing.T) { - discorder.Host = DISCORD_URL - discorder.Delay = time.Duration(100 * time.Millisecond) - err := notifier.AddNotifier(discorder) + Discorder.Host = DISCORD_URL + Discorder.Delay = time.Duration(100 * time.Millisecond) + err := notifier.AddNotifiers(Discorder) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", discorder.Author) - assert.Equal(t, DISCORD_URL, discorder.Host) - }) - - t.Run("Load discord Notifier", func(t *testing.T) { - notifier.Load() + assert.Equal(t, "Hunter Long", Discorder.Author) + assert.Equal(t, DISCORD_URL, Discorder.Host) }) t.Run("discord Notifier Tester", func(t *testing.T) { - assert.True(t, discorder.CanTest()) + assert.True(t, Discorder.CanTest()) }) t.Run("discord Within Limits", func(t *testing.T) { - ok, err := discorder.WithinLimits() + ok, err := Discorder.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) t.Run("discord OnFailure", func(t *testing.T) { - discorder.OnFailure(TestService, TestFailure) - assert.Equal(t, 1, len(discorder.Queue)) + Discorder.OnFailure(TestService, TestFailure) + assert.Equal(t, 1, len(Discorder.Queue)) }) t.Run("discord OnSuccess", func(t *testing.T) { - discorder.OnSuccess(TestService) - assert.Equal(t, 1, len(discorder.Queue)) + Discorder.OnSuccess(TestService) + assert.Equal(t, 1, len(Discorder.Queue)) }) t.Run("discord Check Back Online", func(t *testing.T) { @@ -79,25 +75,25 @@ func TestDiscordNotifier(t *testing.T) { }) t.Run("discord OnSuccess Again", func(t *testing.T) { - discorder.OnSuccess(TestService) - assert.Equal(t, 1, len(discorder.Queue)) + Discorder.OnSuccess(TestService) + assert.Equal(t, 1, len(Discorder.Queue)) }) t.Run("discord Send", func(t *testing.T) { - err := discorder.Send(discordMessage) + err := Discorder.Send(discordMessage) assert.Nil(t, err) }) t.Run("discord Test", func(t *testing.T) { - err := discorder.OnTest() + err := Discorder.OnTest() assert.Nil(t, err) }) t.Run("discord Queue", func(t *testing.T) { - go notifier.Queue(discorder) + go notifier.Queue(Discorder) time.Sleep(1 * time.Second) - assert.Equal(t, DISCORD_URL, discorder.Host) - assert.Equal(t, 0, len(discorder.Queue)) + assert.Equal(t, DISCORD_URL, Discorder.Host) + assert.Equal(t, 0, len(Discorder.Queue)) }) } diff --git a/notifiers/email.go b/notifiers/email.go index d57beee1..d23ec134 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -111,7 +111,7 @@ type email struct { *notifier.Notification } -var emailer = &email{¬ifier.Notification{ +var Emailer = &email{¬ifier.Notification{ Method: "email", Title: "email", Description: "Send emails via SMTP when services are online or offline.", @@ -157,13 +157,6 @@ var emailer = &email{¬ifier.Notification{ }}, }} -func init() { - err := notifier.AddNotifier(emailer) - if err != nil { - panic(err) - } -} - // Send will send the SMTP email with your authentication It accepts type: *emailOutgoing func (u *email) Send(msg interface{}) error { email := msg.(*emailOutgoing) @@ -255,7 +248,7 @@ func (u *email) OnTest() error { } func (u *email) dialSend(email *emailOutgoing) error { - mailer = mail.NewDialer(emailer.Host, emailer.Port, emailer.Username, emailer.Password) + mailer = mail.NewDialer(Emailer.Host, Emailer.Port, Emailer.Username, Emailer.Password) emailSource(email) m := mail.NewMessage() // if email setting TLS is Disabled diff --git a/notifiers/email_test.go b/notifiers/email_test.go index 0e7b3284..fb795247 100644 --- a/notifiers/email_test.go +++ b/notifiers/email_test.go @@ -44,12 +44,12 @@ func init() { EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO") EMAIL_PORT = utils.ToInt(os.Getenv("EMAIL_PORT")) - emailer.Host = EMAIL_HOST - emailer.Username = EMAIL_USER - emailer.Password = EMAIL_PASS - emailer.Var1 = EMAIL_OUTGOING - emailer.Var2 = EMAIL_SEND_TO - emailer.Port = int(EMAIL_PORT) + Emailer.Host = EMAIL_HOST + Emailer.Username = EMAIL_USER + Emailer.Password = EMAIL_PASS + Emailer.Var1 = EMAIL_OUTGOING + Emailer.Var2 = EMAIL_SEND_TO + Emailer.Port = int(EMAIL_PORT) } func TestEmailNotifier(t *testing.T) { @@ -61,36 +61,32 @@ func TestEmailNotifier(t *testing.T) { currentCount = CountNotifiers() t.Run("New Emailer", func(t *testing.T) { - emailer.Host = EMAIL_HOST - emailer.Username = EMAIL_USER - emailer.Password = EMAIL_PASS - emailer.Var1 = EMAIL_OUTGOING - emailer.Var2 = EMAIL_SEND_TO - emailer.Port = int(EMAIL_PORT) - emailer.Delay = time.Duration(100 * time.Millisecond) + Emailer.Host = EMAIL_HOST + Emailer.Username = EMAIL_USER + Emailer.Password = EMAIL_PASS + Emailer.Var1 = EMAIL_OUTGOING + Emailer.Var2 = EMAIL_SEND_TO + Emailer.Port = int(EMAIL_PORT) + Emailer.Delay = time.Duration(100 * time.Millisecond) testEmail = &emailOutgoing{ - To: emailer.GetValue("var2"), + To: Emailer.GetValue("var2"), Subject: fmt.Sprintf("Service %v is Failing", TestService.Name), Template: mainEmailTemplate, Data: TestService, - From: emailer.GetValue("var1"), + From: Emailer.GetValue("var1"), } }) t.Run("Add email Notifier", func(t *testing.T) { - err := notifier.AddNotifier(emailer) + err := notifier.AddNotifiers(Emailer) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", emailer.Author) - assert.Equal(t, EMAIL_HOST, emailer.Host) - }) - - t.Run("Emailer Load", func(t *testing.T) { - notifier.Load() + assert.Equal(t, "Hunter Long", Emailer.Author) + assert.Equal(t, EMAIL_HOST, Emailer.Host) }) t.Run("email Within Limits", func(t *testing.T) { - ok, err := emailer.WithinLimits() + ok, err := Emailer.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) @@ -101,13 +97,13 @@ func TestEmailNotifier(t *testing.T) { }) t.Run("email OnFailure", func(t *testing.T) { - emailer.OnFailure(TestService, TestFailure) - assert.Equal(t, 1, len(emailer.Queue)) + Emailer.OnFailure(TestService, TestFailure) + assert.Equal(t, 1, len(Emailer.Queue)) }) t.Run("email OnSuccess", func(t *testing.T) { - emailer.OnSuccess(TestService) - assert.Equal(t, 1, len(emailer.Queue)) + Emailer.OnSuccess(TestService) + assert.Equal(t, 1, len(Emailer.Queue)) }) t.Run("email Check Back Online", func(t *testing.T) { @@ -115,26 +111,26 @@ func TestEmailNotifier(t *testing.T) { }) t.Run("email OnSuccess Again", func(t *testing.T) { - emailer.OnSuccess(TestService) - assert.Equal(t, 1, len(emailer.Queue)) + Emailer.OnSuccess(TestService) + assert.Equal(t, 1, len(Emailer.Queue)) }) t.Run("email Send", func(t *testing.T) { - err := emailer.Send(testEmail) + err := Emailer.Send(testEmail) assert.Nil(t, err) }) - t.Run("emailer Test", func(t *testing.T) { + t.Run("Emailer Test", func(t *testing.T) { t.SkipNow() - err := emailer.OnTest() + err := Emailer.OnTest() assert.Nil(t, err) }) t.Run("email Run Queue", func(t *testing.T) { - go notifier.Queue(emailer) + go notifier.Queue(Emailer) time.Sleep(6 * time.Second) - assert.Equal(t, EMAIL_HOST, emailer.Host) - assert.Equal(t, 0, len(emailer.Queue)) + assert.Equal(t, EMAIL_HOST, Emailer.Host) + assert.Equal(t, 0, len(Emailer.Queue)) }) } diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index eb6084ee..e3f60cc0 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -33,7 +33,7 @@ type lineNotifier struct { *notifier.Notification } -var lineNotify = &lineNotifier{¬ifier.Notification{ +var LineNotify = &lineNotifier{¬ifier.Notification{ Method: lineNotifyMethod, Title: "LINE Notify", Description: "LINE Notify will send notifications to your LINE Notify account when services are offline or online. Based on the LINE Notify API.", @@ -48,14 +48,6 @@ var lineNotify = &lineNotifier{¬ifier.Notification{ }}}, } -// DEFINE YOUR NOTIFICATION HERE. -func init() { - err := notifier.AddNotifier(lineNotify) - if err != nil { - panic(err) - } -} - // Send will send a HTTP Post with the Authorization to the notify-api.line.me server. It accepts type: string func (u *lineNotifier) Send(msg interface{}) error { message := msg.(string) diff --git a/notifiers/mobile.go b/notifiers/mobile.go index ed238580..a5562efb 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -31,19 +31,19 @@ type mobilePush struct { *notifier.Notification } -var mobile = &mobilePush{¬ifier.Notification{ - Method: "mobile", +var Mobile = &mobilePush{¬ifier.Notification{ + Method: "Mobile", Title: "Mobile Notifications", - Description: `Receive push notifications on your mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the mobile app setup in seconds. + Description: `Receive push notifications on your Mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the Mobile app setup in seconds.

`, Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(5 * time.Second), - Icon: "fas fa-mobile-alt", + Icon: "fas fa-Mobile-alt", Form: []notifier.NotificationForm{{ Type: "text", Title: "Device Identifiers", - Placeholder: "A list of your mobile device push notification ID's.", + Placeholder: "A list of your Mobile device push notification ID's.", DbField: "var1", IsHidden: true, }, { @@ -55,14 +55,6 @@ var mobile = &mobilePush{¬ifier.Notification{ }}}, } -// init the discord notifier -func init() { - err := notifier.AddNotifier(mobile) - if err != nil { - panic(err) - } -} - func (u *mobilePush) Select() *notifier.Notification { return u.Notification } diff --git a/notifiers/mobile_test.go b/notifiers/mobile_test.go index 2e56131f..0638d50b 100644 --- a/notifiers/mobile_test.go +++ b/notifiers/mobile_test.go @@ -31,90 +31,79 @@ var ( func init() { MOBILE_ID = os.Getenv("MOBILE_ID") MOBILE_NUMBER = os.Getenv("MOBILE_NUMBER") - mobile.Var1 = MOBILE_ID + Mobile.Var1 = MOBILE_ID } func TestMobileNotifier(t *testing.T) { t.Parallel() - mobile.Var1 = MOBILE_ID - mobile.Var2 = os.Getenv("MOBILE_NUMBER") + Mobile.Var1 = MOBILE_ID + Mobile.Var2 = os.Getenv("MOBILE_NUMBER") if MOBILE_ID == "" { - t.Log("mobile notifier testing skipped, missing MOBILE_ID environment variable") + t.Log("Mobile notifier testing skipped, missing MOBILE_ID environment variable") t.SkipNow() } currentCount = CountNotifiers() - t.Run("Load mobile", func(t *testing.T) { - mobile.Var1 = MOBILE_ID - mobile.Var2 = MOBILE_NUMBER - mobile.Delay = time.Duration(100 * time.Millisecond) - mobile.Limits = 10 - err := notifier.AddNotifier(mobile) + t.Run("Load Mobile", func(t *testing.T) { + Mobile.Var1 = MOBILE_ID + Mobile.Var2 = MOBILE_NUMBER + Mobile.Delay = time.Duration(100 * time.Millisecond) + Mobile.Limits = 10 + err := notifier.AddNotifiers(Mobile) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", mobile.Author) - assert.Equal(t, MOBILE_ID, mobile.Var1) - assert.Equal(t, MOBILE_NUMBER, mobile.Var2) + assert.Equal(t, "Hunter Long", Mobile.Author) + assert.Equal(t, MOBILE_ID, Mobile.Var1) + assert.Equal(t, MOBILE_NUMBER, Mobile.Var2) }) - t.Run("Load mobile Notifier", func(t *testing.T) { - notifier.Load() + t.Run("Mobile Notifier Tester", func(t *testing.T) { + assert.True(t, Mobile.CanTest()) }) - t.Run("mobile Notifier Tester", func(t *testing.T) { - assert.True(t, mobile.CanTest()) - }) - - t.Run("mobile Within Limits", func(t *testing.T) { - ok, err := mobile.WithinLimits() + t.Run("Mobile Within Limits", func(t *testing.T) { + ok, err := Mobile.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) - t.Run("mobile OnFailure", func(t *testing.T) { - mobile.OnFailure(TestService, TestFailure) - assert.Equal(t, 1, len(mobile.Queue)) + t.Run("Mobile OnFailure", func(t *testing.T) { + Mobile.OnFailure(TestService, TestFailure) + assert.Equal(t, 1, len(Mobile.Queue)) }) - t.Run("mobile OnFailure multiple times", func(t *testing.T) { - for i := 0; i <= 5; i++ { - mobile.OnFailure(TestService, TestFailure) - } - assert.Equal(t, 7, len(mobile.Queue)) + t.Run("Mobile OnSuccess", func(t *testing.T) { + Mobile.OnSuccess(TestService) + assert.Equal(t, 1, len(Mobile.Queue)) }) - t.Run("mobile OnSuccess", func(t *testing.T) { - mobile.OnSuccess(TestService) - assert.Equal(t, 7, len(mobile.Queue)) - }) - - t.Run("mobile OnSuccess Again", func(t *testing.T) { + t.Run("Mobile OnSuccess Again", func(t *testing.T) { t.SkipNow() assert.True(t, TestService.Online) - mobile.OnSuccess(TestService) - assert.Equal(t, 1, len(mobile.Queue)) - go notifier.Queue(mobile) + Mobile.OnSuccess(TestService) + assert.Equal(t, 1, len(Mobile.Queue)) + go notifier.Queue(Mobile) time.Sleep(20 * time.Second) - assert.Equal(t, 1, len(mobile.Queue)) + assert.Equal(t, 1, len(Mobile.Queue)) }) - t.Run("mobile Within Limits again", func(t *testing.T) { - ok, err := mobile.WithinLimits() + t.Run("Mobile Within Limits again", func(t *testing.T) { + ok, err := Mobile.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) - t.Run("mobile Test", func(t *testing.T) { + t.Run("Mobile Test", func(t *testing.T) { t.SkipNow() - err := mobile.OnTest() + err := Mobile.OnTest() assert.Nil(t, err) }) - t.Run("mobile Queue", func(t *testing.T) { + t.Run("Mobile Queue", func(t *testing.T) { t.SkipNow() - go notifier.Queue(mobile) + go notifier.Queue(Mobile) time.Sleep(15 * time.Second) - assert.Equal(t, MOBILE_ID, mobile.Var1) - assert.Equal(t, 0, len(mobile.Queue)) + assert.Equal(t, MOBILE_ID, Mobile.Var1) + assert.Equal(t, 0, len(Mobile.Queue)) }) } diff --git a/notifiers/slack.go b/notifiers/slack.go index 3819e0e0..5ab9491d 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -34,18 +34,11 @@ const ( slackText = `{"text":"{{.}}"}` ) -func init() { - err := notifier.AddNotifier(slacker) - if err != nil { - panic(err) - } -} - type slack struct { *notifier.Notification } -var slacker = &slack{¬ifier.Notification{ +var Slacker = &slack{¬ifier.Notification{ Method: slackMethod, Title: "slack", Description: "Send notifications to your slack channel when a service is offline. Insert your Incoming webhooker URL for your channel to receive notifications. Based on the slack API.", @@ -57,7 +50,7 @@ var slacker = &slack{¬ifier.Notification{ Form: []notifier.NotificationForm{{ Type: "text", Title: "Incoming webhooker Url", - Placeholder: "Insert your slack webhook URL here.", + Placeholder: "Insert your slack Webhook URL here.", SmallText: "Incoming webhooker URL from slack Apps", DbField: "Host", Required: true, @@ -71,7 +64,7 @@ func parseSlackMessage(id int64, temp string, data interface{}) error { if err != nil { return err } - slacker.AddQueue(fmt.Sprintf("service_%v", id), buf.String()) + Slacker.AddQueue(fmt.Sprintf("service_%v", id), buf.String()) return nil } diff --git a/notifiers/slack_test.go b/notifiers/slack_test.go index f5e37175..5a9da0ab 100644 --- a/notifiers/slack_test.go +++ b/notifiers/slack_test.go @@ -30,13 +30,13 @@ var ( func init() { SLACK_URL = os.Getenv("SLACK_URL") - slacker.Host = SLACK_URL + Slacker.Host = SLACK_URL } func TestSlackNotifier(t *testing.T) { t.Parallel() SLACK_URL = os.Getenv("SLACK_URL") - slacker.Host = SLACK_URL + Slacker.Host = SLACK_URL if SLACK_URL == "" { t.Log("slack notifier testing skipped, missing SLACK_URL environment variable") t.SkipNow() @@ -44,76 +44,72 @@ func TestSlackNotifier(t *testing.T) { currentCount = CountNotifiers() t.Run("Load slack", func(t *testing.T) { - slacker.Host = SLACK_URL - slacker.Delay = time.Duration(100 * time.Millisecond) - slacker.Limits = 3 - err := notifier.AddNotifier(slacker) + Slacker.Host = SLACK_URL + Slacker.Delay = time.Duration(100 * time.Millisecond) + Slacker.Limits = 3 + err := notifier.AddNotifiers(Slacker) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", slacker.Author) - assert.Equal(t, SLACK_URL, slacker.Host) - }) - - t.Run("Load slack Notifier", func(t *testing.T) { - notifier.Load() + assert.Equal(t, "Hunter Long", Slacker.Author) + assert.Equal(t, SLACK_URL, Slacker.Host) }) t.Run("slack Notifier Tester", func(t *testing.T) { - assert.True(t, slacker.CanTest()) + assert.True(t, Slacker.CanTest()) }) //t.Run("slack parse message", func(t *testing.T) { // err := parseSlackMessage(slackText, "this is a test!") // assert.Nil(t, err) - // assert.Equal(t, 1, len(slacker.Queue)) + // assert.Equal(t, 1, len(Slacker.Queue)) //}) t.Run("slack Within Limits", func(t *testing.T) { - ok, err := slacker.WithinLimits() + ok, err := Slacker.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) t.Run("slack OnFailure", func(t *testing.T) { - slacker.OnFailure(TestService, TestFailure) - assert.Equal(t, 1, len(slacker.Queue)) + Slacker.OnFailure(TestService, TestFailure) + assert.Equal(t, 1, len(Slacker.Queue)) }) t.Run("slack OnSuccess", func(t *testing.T) { - slacker.OnSuccess(TestService) - assert.Equal(t, 1, len(slacker.Queue)) + Slacker.OnSuccess(TestService) + assert.Equal(t, 1, len(Slacker.Queue)) }) t.Run("slack OnSuccess Again", func(t *testing.T) { assert.True(t, TestService.Online) - slacker.OnSuccess(TestService) - assert.Equal(t, 1, len(slacker.Queue)) - go notifier.Queue(slacker) + Slacker.OnSuccess(TestService) + assert.Equal(t, 1, len(Slacker.Queue)) + go notifier.Queue(Slacker) time.Sleep(15 * time.Second) - assert.Equal(t, 0, len(slacker.Queue)) + assert.Equal(t, 0, len(Slacker.Queue)) }) t.Run("slack Within Limits again", func(t *testing.T) { - ok, err := slacker.WithinLimits() + ok, err := Slacker.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) t.Run("slack Send", func(t *testing.T) { - err := slacker.Send(slackTestMessage) + err := Slacker.Send(slackTestMessage) assert.Nil(t, err) - assert.Equal(t, 0, len(slacker.Queue)) + assert.Equal(t, 0, len(Slacker.Queue)) }) t.Run("slack Test", func(t *testing.T) { - err := slacker.OnTest() + err := Slacker.OnTest() assert.Nil(t, err) }) t.Run("slack Queue", func(t *testing.T) { - go notifier.Queue(slacker) + go notifier.Queue(Slacker) time.Sleep(10 * time.Second) - assert.Equal(t, SLACK_URL, slacker.Host) - assert.Equal(t, 0, len(slacker.Queue)) + assert.Equal(t, SLACK_URL, Slacker.Host) + assert.Equal(t, 0, len(Slacker.Queue)) }) } diff --git a/notifiers/telegram.go b/notifiers/telegram.go index 6215cf43..ec8148db 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -31,7 +31,7 @@ type telegram struct { *notifier.Notification } -var telegramNotifier = &telegram{¬ifier.Notification{ +var Telegram = &telegram{¬ifier.Notification{ Method: "telegram", Title: "Telegram", Description: "Receive notifications on your Telegram channel when a service has an issue. You must get a Telegram API token from the /botfather. Review the Telegram API Tutorial to learn how to generate a new API Token.", @@ -56,14 +56,6 @@ var telegramNotifier = &telegram{¬ifier.Notification{ }}}, } -// DEFINE YOUR NOTIFICATION HERE. -func init() { - err := notifier.AddNotifier(telegramNotifier) - if err != nil { - panic(err) - } -} - func (u *telegram) Select() *notifier.Notification { return u.Notification } diff --git a/notifiers/telegram_test.go b/notifiers/telegram_test.go index 6ed7ff8c..e6ab5f56 100644 --- a/notifiers/telegram_test.go +++ b/notifiers/telegram_test.go @@ -32,8 +32,8 @@ var ( func init() { telegramToken = os.Getenv("TELEGRAM_TOKEN") telegramChannel = os.Getenv("TELEGRAM_CHANNEL") - telegramNotifier.ApiSecret = telegramToken - telegramNotifier.Var1 = telegramChannel + Telegram.ApiSecret = telegramToken + Telegram.Var1 = telegramChannel } func TestTelegramNotifier(t *testing.T) { @@ -46,29 +46,25 @@ func TestTelegramNotifier(t *testing.T) { currentCount = CountNotifiers() t.Run("Load Telegram", func(t *testing.T) { - telegramNotifier.ApiSecret = telegramToken - telegramNotifier.Var1 = telegramChannel - telegramNotifier.Delay = time.Duration(1 * time.Second) - err := notifier.AddNotifier(telegramNotifier) + Telegram.ApiSecret = telegramToken + Telegram.Var1 = telegramChannel + Telegram.Delay = time.Duration(1 * time.Second) + err := notifier.AddNotifiers(Telegram) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", telegramNotifier.Author) - assert.Equal(t, telegramToken, telegramNotifier.ApiSecret) - assert.Equal(t, telegramChannel, telegramNotifier.Var1) - }) - - t.Run("Load Telegram Notifier", func(t *testing.T) { - notifier.Load() + assert.Equal(t, "Hunter Long", Telegram.Author) + assert.Equal(t, telegramToken, Telegram.ApiSecret) + assert.Equal(t, telegramChannel, Telegram.Var1) }) t.Run("Telegram Within Limits", func(t *testing.T) { - ok, err := telegramNotifier.WithinLimits() + ok, err := Telegram.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) t.Run("Telegram OnFailure", func(t *testing.T) { - telegramNotifier.OnFailure(TestService, TestFailure) - assert.Equal(t, 1, len(telegramNotifier.Queue)) + Telegram.OnFailure(TestService, TestFailure) + assert.Equal(t, 1, len(Telegram.Queue)) }) t.Run("Telegram Check Offline", func(t *testing.T) { @@ -76,8 +72,8 @@ func TestTelegramNotifier(t *testing.T) { }) t.Run("Telegram OnSuccess", func(t *testing.T) { - telegramNotifier.OnSuccess(TestService) - assert.Equal(t, 1, len(telegramNotifier.Queue)) + Telegram.OnSuccess(TestService) + assert.Equal(t, 1, len(Telegram.Queue)) }) t.Run("Telegram Check Back Online", func(t *testing.T) { @@ -85,25 +81,25 @@ func TestTelegramNotifier(t *testing.T) { }) t.Run("Telegram OnSuccess Again", func(t *testing.T) { - telegramNotifier.OnSuccess(TestService) - assert.Equal(t, 1, len(telegramNotifier.Queue)) + Telegram.OnSuccess(TestService) + assert.Equal(t, 1, len(Telegram.Queue)) }) t.Run("Telegram Send", func(t *testing.T) { - err := telegramNotifier.Send(telegramMessage) + err := Telegram.Send(telegramMessage) assert.Nil(t, err) }) t.Run("Telegram Test", func(t *testing.T) { - err := telegramNotifier.OnTest() + err := Telegram.OnTest() assert.Nil(t, err) }) t.Run("Telegram Queue", func(t *testing.T) { - go notifier.Queue(telegramNotifier) + go notifier.Queue(Telegram) time.Sleep(3 * time.Second) - assert.Equal(t, telegramToken, telegramNotifier.ApiSecret) - assert.Equal(t, 0, len(telegramNotifier.Queue)) + assert.Equal(t, telegramToken, Telegram.ApiSecret) + assert.Equal(t, 0, len(Telegram.Queue)) }) } diff --git a/notifiers/twilio.go b/notifiers/twilio.go index e660173d..0f381116 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -31,7 +31,7 @@ type twilio struct { *notifier.Notification } -var twilioNotifier = &twilio{¬ifier.Notification{ +var Twilio = &twilio{¬ifier.Notification{ Method: "twilio", Title: "Twilio", Description: "Receive SMS text messages directly to your cellphone when a service is offline. You can use a Twilio test account with limits. This notifier uses the Twilio API.", @@ -66,14 +66,6 @@ var twilioNotifier = &twilio{¬ifier.Notification{ }}}, } -// DEFINE YOUR NOTIFICATION HERE. -func init() { - err := notifier.AddNotifier(twilioNotifier) - if err != nil { - panic(err) - } -} - func (u *twilio) Select() *notifier.Notification { return u.Notification } diff --git a/notifiers/twilio_test.go b/notifiers/twilio_test.go index 11e9e47f..0cac1a1f 100644 --- a/notifiers/twilio_test.go +++ b/notifiers/twilio_test.go @@ -37,10 +37,10 @@ func init() { TWILIO_FROM = os.Getenv("TWILIO_FROM") TWILIO_TO = os.Getenv("TWILIO_TO") - twilioNotifier.ApiKey = TWILIO_SID - twilioNotifier.ApiSecret = TWILIO_SECRET - twilioNotifier.Var1 = TWILIO_TO - twilioNotifier.Var2 = TWILIO_FROM + Twilio.ApiKey = TWILIO_SID + Twilio.ApiSecret = TWILIO_SECRET + Twilio.Var1 = TWILIO_TO + Twilio.Var2 = TWILIO_FROM } func TestTwilioNotifier(t *testing.T) { @@ -52,27 +52,23 @@ func TestTwilioNotifier(t *testing.T) { currentCount = CountNotifiers() t.Run("Load Twilio", func(t *testing.T) { - twilioNotifier.ApiKey = TWILIO_SID - twilioNotifier.Delay = time.Duration(100 * time.Millisecond) - err := notifier.AddNotifier(twilioNotifier) + Twilio.ApiKey = TWILIO_SID + Twilio.Delay = time.Duration(100 * time.Millisecond) + err := notifier.AddNotifiers(Twilio) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", twilioNotifier.Author) - assert.Equal(t, TWILIO_SID, twilioNotifier.ApiKey) - }) - - t.Run("Load Twilio Notifier", func(t *testing.T) { - notifier.Load() + assert.Equal(t, "Hunter Long", Twilio.Author) + assert.Equal(t, TWILIO_SID, Twilio.ApiKey) }) t.Run("Twilio Within Limits", func(t *testing.T) { - ok, err := twilioNotifier.WithinLimits() + ok, err := Twilio.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) t.Run("Twilio OnFailure", func(t *testing.T) { - twilioNotifier.OnFailure(TestService, TestFailure) - assert.Len(t, twilioNotifier.Queue, 1) + Twilio.OnFailure(TestService, TestFailure) + assert.Len(t, Twilio.Queue, 1) }) t.Run("Twilio Check Offline", func(t *testing.T) { @@ -80,8 +76,8 @@ func TestTwilioNotifier(t *testing.T) { }) t.Run("Twilio OnSuccess", func(t *testing.T) { - twilioNotifier.OnSuccess(TestService) - assert.Len(t, twilioNotifier.Queue, 2) + Twilio.OnSuccess(TestService) + assert.Len(t, Twilio.Queue, 2) }) t.Run("Twilio Check Back Online", func(t *testing.T) { @@ -89,25 +85,25 @@ func TestTwilioNotifier(t *testing.T) { }) t.Run("Twilio OnSuccess Again", func(t *testing.T) { - twilioNotifier.OnSuccess(TestService) - assert.Len(t, twilioNotifier.Queue, 2) + Twilio.OnSuccess(TestService) + assert.Len(t, Twilio.Queue, 2) }) t.Run("Twilio Send", func(t *testing.T) { - err := twilioNotifier.Send(twilioMessage) + err := Twilio.Send(twilioMessage) assert.Nil(t, err) }) t.Run("Twilio Test", func(t *testing.T) { - err := twilioNotifier.OnTest() + err := Twilio.OnTest() assert.Nil(t, err) }) t.Run("Twilio Queue", func(t *testing.T) { - go notifier.Queue(twilioNotifier) + go notifier.Queue(Twilio) time.Sleep(1 * time.Second) - assert.Equal(t, TWILIO_SID, twilioNotifier.ApiKey) - assert.Equal(t, 0, len(twilioNotifier.Queue)) + assert.Equal(t, TWILIO_SID, Twilio.ApiKey) + assert.Equal(t, 0, len(Twilio.Queue)) }) } diff --git a/notifiers/webhook.go b/notifiers/webhook.go index d4136ccf..c76b69a9 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -28,14 +28,14 @@ import ( ) const ( - webhookMethod = "webhook" + webhookMethod = "Webhook" ) type webhooker struct { *notifier.Notification } -var webhook = &webhooker{¬ifier.Notification{ +var Webhook = &webhooker{¬ifier.Notification{ Method: webhookMethod, Title: "HTTP webhooker", Description: "Send a custom HTTP request to a specific URL with your own body, headers, and parameters.", @@ -78,14 +78,6 @@ var webhook = &webhooker{¬ifier.Notification{ }, }}} -// DEFINE YOUR NOTIFICATION HERE. -func init() { - err := notifier.AddNotifier(webhook) - if err != nil { - panic(err) - } -} - // Send will send a HTTP Post to the webhooker API. It accepts type: string func (w *webhooker) Send(msg interface{}) error { resp, err := w.sendHttpWebhook(msg.(string)) @@ -158,7 +150,7 @@ func (w *webhooker) OnTest() error { } defer resp.Body.Close() content, err := ioutil.ReadAll(resp.Body) - utils.Log(1, fmt.Sprintf("webhook notifier received: '%v'", string(content))) + utils.Log(1, fmt.Sprintf("Webhook notifier received: '%v'", string(content))) return err } diff --git a/notifiers/webhook_test.go b/notifiers/webhook_test.go index 3ac57715..a513d755 100644 --- a/notifiers/webhook_test.go +++ b/notifiers/webhook_test.go @@ -30,10 +30,10 @@ var ( ) func init() { - webhook.Host = webhookTestUrl - webhook.Var1 = "POST" - webhook.Var2 = webhookMessage - webhook.ApiKey = "application/json" + Webhook.Host = webhookTestUrl + Webhook.Var1 = "POST" + Webhook.Var2 = webhookMessage + Webhook.ApiKey = "application/json" } func TestWebhookNotifier(t *testing.T) { @@ -41,26 +41,18 @@ func TestWebhookNotifier(t *testing.T) { currentCount = CountNotifiers() t.Run("Load webhooker", func(t *testing.T) { - webhook.Host = webhookTestUrl - webhook.Delay = time.Duration(100 * time.Millisecond) - webhook.ApiKey = apiKey - err := notifier.AddNotifier(webhook) + Webhook.Host = webhookTestUrl + Webhook.Delay = time.Duration(100 * time.Millisecond) + Webhook.ApiKey = apiKey + err := notifier.AddNotifiers(Webhook) assert.Nil(t, err) - assert.Equal(t, "Hunter Long", webhook.Author) - assert.Equal(t, webhookTestUrl, webhook.Host) - assert.Equal(t, apiKey, webhook.ApiKey) - }) - - t.Run("Load webhooker Notifier", func(t *testing.T) { - notifier.Load() - }) - - t.Run("Load webhooker Notifier", func(t *testing.T) { - notifier.Load() + assert.Equal(t, "Hunter Long", Webhook.Author) + assert.Equal(t, webhookTestUrl, Webhook.Host) + assert.Equal(t, apiKey, Webhook.ApiKey) }) t.Run("webhooker Notifier Tester", func(t *testing.T) { - assert.True(t, webhook.CanTest()) + assert.True(t, Webhook.CanTest()) }) t.Run("webhooker Replace Body Text", func(t *testing.T) { @@ -69,19 +61,19 @@ func TestWebhookNotifier(t *testing.T) { }) t.Run("webhooker Within Limits", func(t *testing.T) { - ok, err := webhook.WithinLimits() + ok, err := Webhook.WithinLimits() assert.Nil(t, err) assert.True(t, ok) }) t.Run("webhooker OnFailure", func(t *testing.T) { - webhook.OnFailure(TestService, TestFailure) - assert.Len(t, webhook.Queue, 1) + Webhook.OnFailure(TestService, TestFailure) + assert.Len(t, Webhook.Queue, 1) }) t.Run("webhooker OnSuccess", func(t *testing.T) { - webhook.OnSuccess(TestService) - assert.Equal(t, len(webhook.Queue), 1) + Webhook.OnSuccess(TestService) + assert.Equal(t, len(Webhook.Queue), 1) }) t.Run("webhooker Check Back Online", func(t *testing.T) { @@ -89,21 +81,21 @@ func TestWebhookNotifier(t *testing.T) { }) t.Run("webhooker OnSuccess Again", func(t *testing.T) { - webhook.OnSuccess(TestService) - assert.Equal(t, len(webhook.Queue), 1) + Webhook.OnSuccess(TestService) + assert.Equal(t, len(Webhook.Queue), 1) }) t.Run("webhooker Send", func(t *testing.T) { - err := webhook.Send(fullMsg) + err := Webhook.Send(fullMsg) assert.Nil(t, err) - assert.Equal(t, len(webhook.Queue), 1) + assert.Equal(t, len(Webhook.Queue), 1) }) t.Run("webhooker Queue", func(t *testing.T) { - go notifier.Queue(webhook) + go notifier.Queue(Webhook) time.Sleep(8 * time.Second) - assert.Equal(t, webhookTestUrl, webhook.Host) - assert.Equal(t, len(webhook.Queue), 0) + assert.Equal(t, webhookTestUrl, Webhook.Host) + assert.Equal(t, len(Webhook.Queue), 0) }) } diff --git a/source/wiki.go b/source/wiki.go index c74283d8..ed81d785 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,6 +1,6 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-10-24 16:08:58.145719 -0700 PDT m=+0.562621923 +// 2019-10-24 23:09:50.152045 -0700 PDT m=+1.326955115 // // This contains the most recently Markdown source for the Statping Wiki. package source From 5469c01fff5eb8d0a41dab7f49480789f8d6e99d Mon Sep 17 00:00:00 2001 From: zeeZ Date: Fri, 25 Oct 2019 19:21:45 +0200 Subject: [PATCH 040/100] Use hostname from request URL for server name verification. --- utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 4fdee729..601ee7a6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -308,7 +308,7 @@ func HttpRequest(url, method string, content interface{}, headers []string, body transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: !verifySSL, - ServerName: req.Host, + ServerName: req.URL.Hostname(), }, DisableKeepAlives: true, ResponseHeaderTimeout: timeout, From 5bdc254bef0df1acfb61d38e0d51b1fc8cdd6327 Mon Sep 17 00:00:00 2001 From: zeeZ Date: Fri, 25 Oct 2019 23:05:26 +0200 Subject: [PATCH 041/100] Honor header override in hostname verification --- utils/utils.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 601ee7a6..bdc76ffe 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -286,12 +286,15 @@ func HttpRequest(url, method string, content interface{}, headers []string, body if content != nil { req.Header.Set("Content-Type", content.(string)) } + + verifyHost := req.URL.Hostname() for _, h := range headers { keyVal := strings.Split(h, "=") if len(keyVal) == 2 { if keyVal[0] != "" && keyVal[1] != "" { if strings.ToLower(keyVal[0]) == "host" { req.Host = strings.TrimSpace(keyVal[1]) + verifyHost = req.Host } else { req.Header.Set(keyVal[0], keyVal[1]) } @@ -308,7 +311,7 @@ func HttpRequest(url, method string, content interface{}, headers []string, body transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: !verifySSL, - ServerName: req.URL.Hostname(), + ServerName: verifyHost, }, DisableKeepAlives: true, ResponseHeaderTimeout: timeout, From 417ec091a7898b453df440fbc25e9fcc58cae1e9 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 25 Oct 2019 15:32:46 -0700 Subject: [PATCH 042/100] Update .gitattributes --- .gitattributes | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index b552774f..37e6bb8e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ -*.go -text diff=golang -*.gohtml -text diff=golang +*.gohtml linguist-language=golang +*.js linguist-detectable=false +*.yml linguist-detectable=false +*.json linguist-detectable=false +dev/* linguist-vendored From b640bae63f39e439984b5ad5c0ca75c69ab194f2 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 25 Oct 2019 15:34:29 -0700 Subject: [PATCH 043/100] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..136dccb4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://www.buymeacoffee.com/hunterlong'] From 006f02d4d4c372c8d9d4909f13d42d808bffc3b8 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 25 Oct 2019 16:58:12 -0700 Subject: [PATCH 044/100] tests --- core/notifier/example_test.go | 19 ++++++++++++++----- core/notifier/notifiers_test.go | 8 -------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/notifier/example_test.go b/core/notifier/example_test.go index d2c45aba..9fbb231a 100644 --- a/core/notifier/example_test.go +++ b/core/notifier/example_test.go @@ -18,8 +18,9 @@ package notifier import ( "errors" "fmt" - c "github.com/hunterlong/statping/core" + "github.com/hunterlong/statping/source" "github.com/hunterlong/statping/types" + "github.com/hunterlong/statping/utils" "time" ) @@ -84,7 +85,11 @@ var example = &ExampleNotifier{&Notification{ // init will be ran when Statping is loaded, AddNotifier will add the notifier instance to the system func init() { - c.AttachNotifiers() + dir = utils.Directory + source.Assets() + utils.InitLogs() + injectDatabase() + AddNotifiers(example) } // Send is the main function to hold your notifier functionality @@ -227,7 +232,9 @@ func ExampleNotification_OnSuccess() { msg := fmt.Sprintf("this is a successful message as a string passing into AddQueue function") example.AddQueue("example", msg) fmt.Println(len(example.Queue)) - // Output: 1 + // Output: + // Notifier 'Example' added new item (example) to the queue. (1 queued) + // 1 } // Add a new message into the queue OnSuccess @@ -253,7 +260,7 @@ func ExampleOnTest() { func ExampleNotification_CanTest() { testable := example.CanTest() fmt.Print(testable) - // Output: false + // Output: true } // Add any type of interface to the AddQueue function to be ran in the queue @@ -262,7 +269,9 @@ func ExampleNotification_AddQueue() { example.AddQueue("example", msg) queue := example.Queue fmt.Printf("Example has %v items in the queue", len(queue)) - // Output: Example has 2 items in the queue + // Output: + // Notifier 'Example' added new item (example) to the queue. (2 queued) + // Example has 2 items in the queue } // The Send method will run the main functionality of your notifier diff --git a/core/notifier/notifiers_test.go b/core/notifier/notifiers_test.go index b7fd78e4..d6a2d8e9 100644 --- a/core/notifier/notifiers_test.go +++ b/core/notifier/notifiers_test.go @@ -17,7 +17,6 @@ package notifier import ( "fmt" - "github.com/hunterlong/statping/source" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "github.com/jinzhu/gorm" @@ -56,13 +55,6 @@ var core = &types.Core{ Name: "testing notifiers", } -func init() { - dir = utils.Directory - source.Assets() - utils.InitLogs() - injectDatabase() -} - func injectDatabase() { utils.DeleteFile(dir + "/statup.db") db, _ = gorm.Open("sqlite3", dir+"/statup.db") From f0447d673663a13177291dec2387675cdfe333a7 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Tue, 5 Nov 2019 09:00:33 -0800 Subject: [PATCH 045/100] Update FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 136dccb4..062927ab 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: hunterlong custom: ['https://www.buymeacoffee.com/hunterlong'] From b25b96ef0263a198914e4f27620ec15e13786d28 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 5 Nov 2019 09:59:14 -0800 Subject: [PATCH 046/100] fixed mobile issue --- Makefile | 2 +- notifiers/mobile.go | 4 ++-- source/wiki.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 05a1e1e8..4df7032c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GOCMD=go GOBUILD=$(GOCMD) build -a GOTEST=$(GOCMD) test GOGET=$(GOCMD) get -GOVERSION=1.12.x +GOVERSION=1.13.x GOINSTALL=$(GOCMD) install XGO=GOPATH=$(GOPATH) xgo -go $(GOVERSION) --dest=build BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" diff --git a/notifiers/mobile.go b/notifiers/mobile.go index a5562efb..277d10f7 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -32,14 +32,14 @@ type mobilePush struct { } var Mobile = &mobilePush{¬ifier.Notification{ - Method: "Mobile", + Method: "mobile", Title: "Mobile Notifications", Description: `Receive push notifications on your Mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the Mobile app setup in seconds.

`, Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(5 * time.Second), - Icon: "fas fa-Mobile-alt", + Icon: "fas fa-mobile-alt", Form: []notifier.NotificationForm{{ Type: "text", Title: "Device Identifiers", diff --git a/source/wiki.go b/source/wiki.go index ed81d785..9dcc0d82 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,6 +1,6 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-10-24 23:09:50.152045 -0700 PDT m=+1.326955115 +// 2019-11-05 09:40:38.659073 -0800 PST m=+0.675924642 // // This contains the most recently Markdown source for the Statping Wiki. package source From f5ceed84cf9f7f61649958c3c3383fadd2449f55 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 5 Nov 2019 11:27:49 -0800 Subject: [PATCH 047/100] xgo --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 4df7032c..6a6a7c29 100644 --- a/Makefile +++ b/Makefile @@ -6,14 +6,14 @@ GOCMD=go GOBUILD=$(GOCMD) build -a GOTEST=$(GOCMD) test GOGET=$(GOCMD) get -GOVERSION=1.13.x +GOVERSION=1.13.1 GOINSTALL=$(GOCMD) install XGO=GOPATH=$(GOPATH) xgo -go $(GOVERSION) --dest=build BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" RICE=$(GOPATH)/bin/rice PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH) PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }' -TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "os": [ "linux" ], "language": "go", "go": [ "${GOVERSION}" ], "go_import_path": "github.com/hunterlong/statping", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "${VERSION}" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "before_deploy": [ "git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file_glob": true, "file": "build/*", "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "wget -O statping.gpg $(SIGN_URL)", "gpg --import statping.gpg", "travis_wait 30 docker pull karalabe/xgo-latest", "make release" ], "after_success": [], "after_deploy": [ "make publish-homebrew" ] } } }' +TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "os": [ "linux" ], "language": "go", "go": [ "${GOVERSION}" ], "go_import_path": "github.com/hunterlong/statping", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "${VERSION}" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "before_deploy": [ "git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file_glob": true, "file": "build/*", "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "wget -O statping.gpg $(SIGN_URL)", "gpg --import statping.gpg", "travis_wait 30 docker pull crazymax/xgo:$(GOVERSION)", "make release" ], "after_success": [], "after_deploy": [ "make publish-homebrew" ] } } }' TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statping PATH:=$(PATH) @@ -220,7 +220,7 @@ dev-deps: $(GOINSTALL) github.com/mattn/goveralls $(GOGET) github.com/rendon/testcli $(GOGET) github.com/robertkrimen/godocdown/godocdown - $(GOGET) github.com/karalabe/xgo + $(GOGET) github.com/crazy-max/xgo $(GOGET) github.com/GeertJohan/go.rice $(GOGET) github.com/GeertJohan/go.rice/rice $(GOINSTALL) github.com/GeertJohan/go.rice/rice @@ -344,8 +344,8 @@ valid-sign: # install xgo and pull the xgo docker image xgo-install: clean - go get github.com/karalabe/xgo - docker pull karalabe/xgo-latest + go get github.com/crazy-max/xgo + docker pull crazy-max/xgo:${GOVERSION} heroku: git push heroku master From 6d78fceffc8009b6bb5fc30f161a566f3ab84649 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Tue, 5 Nov 2019 19:35:01 -0800 Subject: [PATCH 048/100] quick notifier fix --- core/notifier/notifiers.go | 21 +++++++++++---------- source/tmpl/settings.gohtml | 9 +++------ source/wiki.go | 4 ++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/core/notifier/notifiers.go b/core/notifier/notifiers.go index 7d97d4dd..1de0e1b6 100644 --- a/core/notifier/notifiers.go +++ b/core/notifier/notifiers.go @@ -102,7 +102,7 @@ func (n *Notification) AfterFind() (err error) { func (n *Notification) AddQueue(uid string, msg interface{}) { data := &QueueData{uid, msg} n.Queue = append(n.Queue, data) - utils.Log(0, fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) + utils.Log(1, fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) } // CanTest returns true if the notifier implements the OnTest interface @@ -189,8 +189,8 @@ func reverseLogs(input []*NotificationLog) []*NotificationLog { } // isInDatabase returns true if the notifier has already been installed -func isInDatabase(n *Notification) bool { - inDb := modelDb(n).RecordNotFound() +func isInDatabase(n Notifier) bool { + inDb := modelDb(n.Select()).RecordNotFound() return !inDb } @@ -216,13 +216,14 @@ func Update(n Notifier, notif *Notification) (*Notification, error) { } // insertDatabase will create a new record into the database for the notifier -func insertDatabase(n *Notification) (int64, error) { - n.Limits = 3 - query := db.Create(n) +func insertDatabase(n Notifier) (int64, error) { + noti := n.Select() + noti.Limits = 3 + query := db.Create(noti) if query.Error != nil { return 0, query.Error } - return n.Id, query.Error + return noti.Id, query.Error } // SelectNotifier returns the Notification struct from the database @@ -237,7 +238,7 @@ func SelectNotifier(method string) (*Notification, Notifier, error) { return notifier, comm.(Notifier), nil } } - return nil, nil, nil + return nil, nil, errors.New("cannot find notifier") } // Init accepts the Notifier interface to initialize the notifier @@ -309,9 +310,9 @@ CheckNotifier: // install will check the database for the notification, if its not inserted it will insert a new record for it func install(n Notifier) error { - inDb := isInDatabase(n.Select()) + inDb := isInDatabase(n) if !inDb { - _, err := insertDatabase(n.Select()) + _, err := insertDatabase(n) if err != nil { utils.Log(3, err) return err diff --git a/source/tmpl/settings.gohtml b/source/tmpl/settings.gohtml index 05324939..275521f5 100644 --- a/source/tmpl/settings.gohtml +++ b/source/tmpl/settings.gohtml @@ -140,7 +140,7 @@

Bulk Import Services

You can import multiple services based on a CSV file with the format shown on the Bulk Import Wiki. -
+
@@ -158,16 +158,13 @@
- Export Settings - {{if .Domain}} - Authentication QR Code - {{end}} - {{if .Domain}}
+ Open in Statping App + Export Settings {{end}}
diff --git a/source/wiki.go b/source/wiki.go index 9dcc0d82..6a68081f 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,9 +1,9 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-11-05 09:40:38.659073 -0800 PST m=+0.675924642 +// 2019-11-05 19:03:57.719603 -0800 PST m=+1.160734429 // // This contains the most recently Markdown source for the Statping Wiki. package source // CompiledWiki contains all of the Statping Wiki pages from the Github Wiki repo. -var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Upgrading Staping\nYou can upgrade the Statping executable by running the commands below on your EC2 instance.\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-x64.tar.gz\ntar -xvzf statping-linux-x64.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n```\nYou can test the version number by running `statping version`.\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/hunterlong/statping/core/notifier)\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSLMODE=false # enable ssl_mode for postgres (To enable use require)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") +var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Upgrading Staping\nYou can upgrade the Statping executable by running the commands below on your EC2 instance.\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-x64.tar.gz\ntar -xvzf statping-linux-x64.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n```\nYou can test the version number by running `statping version`.\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n# Adding Notifiers\nTo add a notifier to the Statping application, simply append your Notifier in the `AttachNotifiers()` function inside of [core/core.go](https://github.com/hunterlong/statping/blob/master/core/core.go).\n\n```go\n// AttachNotifiers will attach all the notifier's into the system\nfunc AttachNotifiers() error {\n\treturn notifier.AddNotifiers(\n\t\tnotifiers.Command,\n\t\tnotifiers.Discorder,\n\t\tnotifiers.Emailer,\n\t\tnotifiers.LineNotify,\n\t\tnotifiers.Mobile,\n\t\tnotifiers.Slacker,\n\t\tnotifiers.Telegram,\n\t\tnotifiers.Twilio,\n\t\tnotifiers.Webhook,\n\t)\n}\n```\n###### [AttachNotifiers](https://github.com/hunterlong/statping/blob/master/core/core.go#L183)\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSLMODE=false # enable ssl_mode for postgres (To enable use require)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") From 47bda5215f493682fb1ed29d377f0db6a7540fb6 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 22 Nov 2019 10:42:58 -0800 Subject: [PATCH 049/100] go modules --- Gopkg.lock | 338 -------------- Gopkg.toml | 94 ---- Makefile | 10 +- go.mod | 34 ++ source/tmpl/postman.json | 978 +++++++++++++++++++++++---------------- 5 files changed, 624 insertions(+), 830 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 go.mod diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index db826539..00000000 --- a/Gopkg.lock +++ /dev/null @@ -1,338 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:ecb63d398130fee54aec9b9aa80df16b72c9d785255735bd5a30aa8889758a37" - name = "github.com/99designs/gqlgen" - packages = [ - "complexity", - "graphql", - "graphql/introspection", - "handler", - ] - pruneopts = "UT" - revision = "efb6efe06c6e4fc706440acebf6f81fff85f295c" - version = "v0.10.1" - -[[projects]] - digest = "1:07f7314344b2771963ada0b2a4a426c59d782dac227dcfff2499188a186446c0" - name = "github.com/GeertJohan/go.rice" - packages = [ - ".", - "embedded", - ] - pruneopts = "UT" - revision = "cd53cd147dd5288bc2fb990fb983e58e301abb5e" - version = "v1.0.0" - -[[projects]] - digest = "1:786e862ec180708b60ee670723e3edd969fd4309e7b1c315cd7de058ac62a011" - name = "github.com/agnivade/levenshtein" - packages = ["."] - pruneopts = "UT" - revision = "51b298ff305e72cfd29166dccc3f9878e82f9fdc" - version = "v1.0.2" - -[[projects]] - digest = "1:f1ec92a2b8473612547f6e13edbc8c8e6cda6c8be9c54b31958aad4a7ccaaa2b" - name = "github.com/ararog/timeago" - packages = ["."] - pruneopts = "UT" - revision = "518814407569bf983ea81e1bf8b550dd4e7b34f3" - version = "0.0.1" - -[[projects]] - digest = "1:8e8da6cc8cca12851d4e089d970a0f7387b3a6bcc8c459ff432213b03076a66d" - name = "github.com/daaku/go.zipexe" - packages = ["."] - pruneopts = "UT" - revision = "74d766ac1dde7458348221869a7d1e7e5fa0597e" - version = "v1.0.1" - -[[projects]] - digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" - name = "github.com/davecgh/go-spew" - packages = ["spew"] - pruneopts = "UT" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" - -[[projects]] - digest = "1:3806f369b846160fcbde19bdcf93790868defe7c58d1bb6bc8d974c5b8f8dc1e" - name = "github.com/go-mail/mail" - packages = ["."] - pruneopts = "UT" - revision = "f59b9b83a4e522098e3d3eb94e6f81850ad6e973" - version = "v2.3.1" - -[[projects]] - digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" - name = "github.com/go-sql-driver/mysql" - packages = ["."] - pruneopts = "UT" - revision = "72cd26f257d44c1114970e19afddcd812016007e" - version = "v1.4.1" - -[[projects]] - digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" - name = "github.com/go-yaml/yaml" - packages = ["."] - pruneopts = "UT" - revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" - version = "v2.2.4" - -[[projects]] - digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296" - name = "github.com/gorilla/mux" - packages = ["."] - pruneopts = "UT" - revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15" - version = "v1.7.3" - -[[projects]] - digest = "1:e72d1ebb8d395cf9f346fd9cbc652e5ae222dd85e0ac842dc57f175abed6d195" - name = "github.com/gorilla/securecookie" - packages = ["."] - pruneopts = "UT" - revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983" - version = "v1.1.1" - -[[projects]] - digest = "1:172c862eabc72e90f461bcef223c49869628bec6d989386dfb03281ae3222148" - name = "github.com/gorilla/sessions" - packages = ["."] - pruneopts = "UT" - revision = "4355a998706e83fe1d71c31b07af94e34f68d74a" - version = "v1.2.0" - -[[projects]] - digest = "1:e62657cca9badaa308d86e7716083e4c5933bb78e30a17743fc67f50be26f6f4" - name = "github.com/gorilla/websocket" - packages = ["."] - pruneopts = "UT" - revision = "c3e18be99d19e6b3e8f1559eea2c161a665c4b6b" - version = "v1.4.1" - -[[projects]] - digest = "1:c77361e611524ec8f2ad37c408c3c916111a70b6acf806a1200855696bf8fa4d" - name = "github.com/hashicorp/golang-lru" - packages = [ - ".", - "simplelru", - ] - pruneopts = "UT" - revision = "7f827b33c0f158ec5dfbba01bb0b14a4541fd81d" - version = "v0.5.3" - -[[projects]] - digest = "1:dbdfa0171086e696a3e72527a13b02e653b7e03cd168618f505bd71bc6a3257b" - name = "github.com/jinzhu/gorm" - packages = [ - ".", - "dialects/mysql", - "dialects/postgres", - "dialects/sqlite", - ] - pruneopts = "UT" - revision = "81c17a7e2529c59efc4e74c5b32c1fb71fb12fa2" - version = "v1.9.11" - -[[projects]] - digest = "1:01ed62f8f4f574d8aff1d88caee113700a2b44c42351943fa73cc1808f736a50" - name = "github.com/jinzhu/inflection" - packages = ["."] - pruneopts = "UT" - revision = "f5c5f50e6090ae76a29240b61ae2a90dd810112e" - version = "v1.0.0" - -[[projects]] - digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b" - name = "github.com/joho/godotenv" - packages = ["."] - pruneopts = "UT" - revision = "23d116af351c84513e1946b527c88823e476be13" - version = "v1.3.0" - -[[projects]] - digest = "1:0ead8e64fe356bd9221605e3ec40b4438509868018cbbbaaaff3ebae1b69b78b" - name = "github.com/lib/pq" - packages = [ - ".", - "hstore", - "oid", - "scram", - ] - pruneopts = "UT" - revision = "3427c32cb71afc948325f299f040e53c1dd78979" - version = "v1.2.0" - -[[projects]] - digest = "1:79e87abf06b873987dee86598950f5b51732ac454d5a5cab6445a14330e6c9e3" - name = "github.com/mattn/go-sqlite3" - packages = ["."] - pruneopts = "UT" - revision = "b612a2feea6aa87c6d052d9086572551df06497e" - version = "v1.11.0" - -[[projects]] - digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - pruneopts = "UT" - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - branch = "master" - digest = "1:8dd6663207b795abbe94a20d2785c9eb16be59183f5468e8816f98aeda466c7f" - name = "github.com/rendon/testcli" - packages = ["."] - pruneopts = "UT" - revision = "6283090d169f51a2410b4e260341a01c9a4c0ca7" - -[[projects]] - digest = "1:9421f6e9e28ef86933e824b5caff441366f2b69bb281085b9dca40e1f27a1602" - name = "github.com/shurcooL/sanitized_anchor_name" - packages = ["."] - pruneopts = "UT" - revision = "7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615" - version = "v1.0.0" - -[[projects]] - digest = "1:8548c309c65a85933a625be5e7d52b6ac927ca30c56869fae58123b8a77a75e1" - name = "github.com/stretchr/testify" - packages = ["assert"] - pruneopts = "UT" - revision = "221dbe5ed46703ee255b1da0dec05086f5035f62" - version = "v1.4.0" - -[[projects]] - branch = "master" - digest = "1:9f3a60def1a1eb5ac184e71dde43c6f99606f54d106db7c95f8b8338629a777b" - name = "github.com/tatsushid/go-fastping" - packages = ["."] - pruneopts = "UT" - revision = "d7bb493dee3e090e2ffb6914adddf17c1e7c026c" - -[[projects]] - digest = "1:b4e8aaca88f799355f4ac560bce4293fb85ff21003dd0d5741ca503f7a788e91" - name = "github.com/vektah/gqlparser" - packages = [ - ".", - "ast", - "gqlerror", - "lexer", - "parser", - "validator", - "validator/rules", - ] - pruneopts = "UT" - revision = "05741cdb0871330d8bc980d4afd21ab34eceee83" - version = "v1.1.2" - -[[projects]] - branch = "master" - digest = "1:9d5b5d543996dd584da1db1e0de1926f3e4c3a8dba0fa2f8db70f3ebee2342e0" - name = "golang.org/x/crypto" - packages = [ - "bcrypt", - "blowfish", - ] - pruneopts = "UT" - revision = "87dc89f01550277dc22b74ffcf4cd89fa2f40f4c" - -[[projects]] - branch = "master" - digest = "1:b1c9d783fea9a0529d1aff7f1e3ffe9bfa474ad65a273d78c98d43e77c1fdd31" - name = "golang.org/x/net" - packages = [ - "bpf", - "icmp", - "internal/iana", - "internal/socket", - "ipv4", - "ipv6", - ] - pruneopts = "UT" - revision = "da9a3fd4c5820e74b24a6cb7fb438dc9b0dd377c" - -[[projects]] - branch = "master" - digest = "1:9f5be1dbb5091bb62d83fbb6db8e794adb8f0e05424679d6f15a17b670e38b4c" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "UT" - revision = "b09406accb4736d857a32bf9444cd7edae2ffa79" - -[[projects]] - digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" - name = "google.golang.org/appengine" - packages = ["cloudsql"] - pruneopts = "UT" - revision = "971852bfffca25b069c31162ae8f247a3dba083b" - version = "v1.6.5" - -[[projects]] - branch = "v3" - digest = "1:7388652e2215a3f45d341d58766ed58317971030eb1cbd75f005f96ace8e9196" - name = "gopkg.in/alexcesaro/quotedprintable.v3" - packages = ["."] - pruneopts = "UT" - revision = "2caba252f4dc53eaf6b553000885530023f54623" - -[[projects]] - digest = "1:c805e517269b0ba4c21ded5836019ed7d16953d4026cb7d00041d039c7906be9" - name = "gopkg.in/natefinch/lumberjack.v2" - packages = ["."] - pruneopts = "UT" - revision = "a96e63847dc3c67d17befa69c303767e2f84e54f" - version = "v2.1" - -[[projects]] - digest = "1:2ee0f15eb0fb04f918db7c2dcf39745f40d69f798ef171610a730e8a56aaa4fd" - name = "gopkg.in/russross/blackfriday.v2" - packages = ["."] - pruneopts = "UT" - revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135" - version = "v2.0.1" - -[[projects]] - digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "UT" - revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" - version = "v2.2.4" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/99designs/gqlgen/graphql", - "github.com/99designs/gqlgen/graphql/introspection", - "github.com/99designs/gqlgen/handler", - "github.com/GeertJohan/go.rice", - "github.com/ararog/timeago", - "github.com/go-mail/mail", - "github.com/go-yaml/yaml", - "github.com/gorilla/mux", - "github.com/gorilla/sessions", - "github.com/jinzhu/gorm", - "github.com/jinzhu/gorm/dialects/mysql", - "github.com/jinzhu/gorm/dialects/postgres", - "github.com/jinzhu/gorm/dialects/sqlite", - "github.com/joho/godotenv", - "github.com/rendon/testcli", - "github.com/stretchr/testify/assert", - "github.com/tatsushid/go-fastping", - "github.com/vektah/gqlparser", - "github.com/vektah/gqlparser/ast", - "golang.org/x/crypto/bcrypt", - "gopkg.in/natefinch/lumberjack.v2", - "gopkg.in/russross/blackfriday.v2", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 983ecf5f..00000000 --- a/Gopkg.toml +++ /dev/null @@ -1,94 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/99designs/gqlgen" - version = "0.10.1" - -[[constraint]] - name = "github.com/GeertJohan/go.rice" - version = "1.0.0" - -[[constraint]] - name = "github.com/ararog/timeago" - version = "0.0.1" - -[[constraint]] - name = "github.com/go-mail/mail" - version = "2.3.1" - -[[constraint]] - name = "github.com/go-yaml/yaml" - version = "2.2.4" - -[[constraint]] - name = "github.com/gorilla/mux" - version = "1.7.3" - -[[constraint]] - name = "github.com/gorilla/sessions" - version = "1.2.0" - -[[constraint]] - name = "github.com/jinzhu/gorm" - version = "1.9.11" - -[[constraint]] - name = "github.com/joho/godotenv" - version = "1.3.0" - -[[constraint]] - branch = "master" - name = "github.com/rendon/testcli" - -[[constraint]] - name = "github.com/stretchr/testify" - version = "1.4.0" - -[[constraint]] - branch = "master" - name = "github.com/tatsushid/go-fastping" - -[[constraint]] - name = "github.com/vektah/gqlparser" - version = "1.1.2" - -[[constraint]] - branch = "master" - name = "golang.org/x/crypto" - -[[constraint]] - name = "gopkg.in/natefinch/lumberjack.v2" - version = "2.1.0" - -[[constraint]] - name = "gopkg.in/russross/blackfriday.v2" - version = "2.0.1" - -[prune] - go-tests = true - unused-packages = true diff --git a/Makefile b/Makefile index 6a6a7c29..876d0ddf 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ snapcraft: clean snapcraft-build snapcraft-release # build Statping for local arch build: compile + go mod vendor $(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd # build Statping plugins @@ -204,14 +205,6 @@ databases: docker run --name statping_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password123 -e MYSQL_DATABASE=root -d mysql sleep 30 - -# -# Download and Install dependencies -# -# run dep to install all required golang dependecies -dep: - dep ensure -vendor-only - # install all required golang dependecies dev-deps: $(GOGET) github.com/stretchr/testify/assert @@ -232,7 +225,6 @@ dev-deps: $(GOGET) github.com/ararog/timeago $(GOGET) gopkg.in/natefinch/lumberjack.v2 $(GOGET) golang.org/x/crypto/bcrypt - $(GOGET) github.com/99designs/gqlgen/... # remove files for a clean compile/build clean: diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..69a38252 --- /dev/null +++ b/go.mod @@ -0,0 +1,34 @@ +module github.com/hunterlong/statping + +go 1.13 + +require ( + github.com/99designs/gqlgen v0.10.1 + github.com/GeertJohan/go.rice v1.0.0 + github.com/agnivade/levenshtein v1.0.2 // indirect + github.com/ararog/timeago v0.0.0-20160324182854-518814407569 + github.com/daaku/go.zipexe v1.0.1 // indirect + github.com/go-mail/mail v2.3.1+incompatible + github.com/go-yaml/yaml v2.1.0+incompatible + github.com/gorilla/mux v1.7.3 + github.com/gorilla/sessions v1.2.0 + github.com/gorilla/websocket v1.4.1 // indirect + github.com/hashicorp/golang-lru v0.5.3 // indirect + github.com/jinzhu/gorm v1.9.11 + github.com/joho/godotenv v1.3.0 + github.com/lib/pq v1.2.0 // indirect + github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/stretchr/testify v1.4.0 + github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e + github.com/vektah/gqlparser v1.1.2 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect + golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect + google.golang.org/appengine v1.6.5 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 + gopkg.in/yaml.v2 v2.2.7 // indirect +) diff --git a/source/tmpl/postman.json b/source/tmpl/postman.json index 9c16ce81..39e1c167 100644 --- a/source/tmpl/postman.json +++ b/source/tmpl/postman.json @@ -2,165 +2,10 @@ "info": { "_postman_id": "94807b85-ef65-4370-9144-b1a74e04cb0e", "name": "Statping", - "description": "Statup API Requests", + "description": "Statping API Documentation for all endpoints to manage your services, users, groups, notifiers, messages, and more. \n\n## API Requirements\n\n- `endpoint` variable should be the URL of your Statping instance.\n- `api_key` variable is the API Secret key from the Settings page and is used for the Authorization Bearer token.\n\n", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ - { - "name": "Main", - "item": [ - { - "name": "Statping Details", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "pm.test(\"Check Core API Route\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.name).to.eql(\"Statping\");", - " pm.expect(jsonData.description).to.eql(\"Statping Monitoring Sample Data\");", - " pm.expect(jsonData.using_cdn).to.eql(false);", - " pm.expect(jsonData.database).to.eql(\"sqlite\");", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{endpoint}}/api", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api" - ] - }, - "description": "The root API route to view basic Statping settings." - }, - "response": [] - }, - { - "name": "Statping Clear Cache", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{endpoint}}/api/clear_cache", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "clear_cache" - ] - }, - "description": "The root API route to view basic Statping settings." - }, - "response": [] - }, - { - "name": "Statping Reset API Tokens", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{endpoint}}/api/renew", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "renew" - ] - }, - "description": "The root API route to view basic Statping settings." - }, - "response": [] - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - } - }, { "name": "Services", "item": [ @@ -172,6 +17,10 @@ "script": { "id": "d87f8a4e-7640-45b8-9d45-4f6e6f2463ee", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View All Services\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.length).to.eql(5);", @@ -194,10 +43,6 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/services", "host": [ @@ -220,6 +65,10 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View Service\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.name).to.eql(\"Google\");", @@ -251,10 +100,6 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/services/1", "host": [ @@ -281,10 +126,6 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/services/1", "host": [ @@ -327,7 +168,9 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -353,12 +196,8 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{endpoint}}/api/services/1/data", + "raw": "{{endpoint}}/api/services/1/data?start=0&end=1973064434&group=hour", "host": [ "{{endpoint}}" ], @@ -367,8 +206,26 @@ "services", "1", "data" + ], + "query": [ + { + "key": "start", + "value": "0", + "description": "Starting from time (unix timestamp)" + }, + { + "key": "end", + "value": "1973064434", + "description": "End on time (unix timestamp)" + }, + { + "key": "group", + "value": "hour", + "description": "Increment grouping (minute, hour, day)" + } ] - } + }, + "description": "View the chart data for the service's latency. The response returns an array of objects as `x` for timestamp, and `y` for value in ascending order." }, "response": [] }, @@ -380,7 +237,9 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -406,12 +265,8 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{endpoint}}/api/services/1/ping", + "raw": "{{endpoint}}/api/services/1/ping?start=0&end=1973064434&group=hour", "host": [ "{{endpoint}}" ], @@ -420,8 +275,26 @@ "services", "1", "ping" + ], + "query": [ + { + "key": "start", + "value": "0", + "description": "Starting from time (unix timestamp)" + }, + { + "key": "end", + "value": "1973064434", + "description": "End on time (unix timestamp)" + }, + { + "key": "group", + "value": "hour", + "description": "Increment grouping (minute, hour, day)" + } ] - } + }, + "description": "View the chart data for the service's ping response time. The response returns an array of objects as `x` for timestamp, and `y` for value in ascending order." }, "response": [] }, @@ -433,7 +306,9 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -459,10 +334,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/services/1/heatmap", "host": [ @@ -474,7 +345,8 @@ "1", "heatmap" ] - } + }, + "description": "View the data service failures in a heatmap. The field `x` is the day of the month, and `y` is the amount of failures the service had." }, "response": [] }, @@ -486,7 +358,9 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -512,10 +386,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/services/1/failures", "host": [ @@ -527,7 +397,8 @@ "1", "failures" ] - } + }, + "description": "Returns an array of failures for this service. It includes the error message, http status code, and the ping response time." }, "response": [] }, @@ -539,6 +410,10 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Service Successful Hits\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.length).to.be.at.least(100);", @@ -568,10 +443,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/services/1/hits", "host": [ @@ -583,7 +454,8 @@ "1", "hits" ] - } + }, + "description": "Returns on array of all the successful hits for this service." }, "response": [] }, @@ -595,7 +467,9 @@ "script": { "id": "b5a67a19-fd08-40b0-a961-3e9474ab78c6", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -693,6 +567,10 @@ "script": { "id": "d4eb16fe-8495-40e5-9ca3-be20951e5133", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Create Service\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.output.name).to.eql(\"New Service\");", @@ -797,6 +675,10 @@ "script": { "id": "b5a67a19-fd08-40b0-a961-3e9474ab78c6", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Update Service\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.output.name).to.eql(\"Updated New Service\");", @@ -902,6 +784,10 @@ "script": { "id": "dd4d721d-d874-448b-abc9-59c1afceb58e", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Delete Service\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", @@ -941,7 +827,7 @@ "{{service_id}}" ] }, - "description": "Delete a service and stop monitoring." + "description": "Delete a service and stops monitoring." }, "response": [ { @@ -995,7 +881,9 @@ "script": { "id": "dd4d721d-d874-448b-abc9-59c1afceb58e", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -1030,11 +918,12 @@ "failures" ] }, - "description": "Delete a service and stop monitoring." + "description": "Delete all the service failures." }, "response": [] } ], + "description": "With the Statping API, you can add, remove, edit all your services fields from the API directly. This includes viewing Service chart data for latency/up-time, and even viewing a log of failures. ", "auth": { "type": "bearer", "bearer": [ @@ -1066,7 +955,8 @@ ] } } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Groups", @@ -1079,6 +969,10 @@ "script": { "id": "d87f8a4e-7640-45b8-9d45-4f6e6f2463ee", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View All Groups\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.length).to.eql(3);", @@ -1101,10 +995,6 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/groups", "host": [ @@ -1123,10 +1013,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/groups", "host": [ @@ -1168,6 +1054,10 @@ "script": { "id": "023c5643-6cb1-4cd0-b775-566f232d68f8", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View Group\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.name).to.eql(\"Main Services\");", @@ -1197,10 +1087,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/groups/1", "host": [ @@ -1227,10 +1113,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/groups/1", "host": [ @@ -1273,6 +1155,10 @@ "script": { "id": "d4eb16fe-8495-40e5-9ca3-be20951e5133", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Create Group\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.output.name).to.eql(\"New Group\");", @@ -1373,7 +1259,9 @@ "script": { "id": "b5a67a19-fd08-40b0-a961-3e9474ab78c6", "exec": [ - "" + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" ], "type": "text/javascript" } @@ -1424,6 +1312,10 @@ "script": { "id": "dd4d721d-d874-448b-abc9-59c1afceb58e", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Delete Service\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", @@ -1463,7 +1355,7 @@ "{{group_id}}" ] }, - "description": "Delete a group" + "description": "Delete a group and removes all services attached to this group." }, "response": [ { @@ -1510,6 +1402,7 @@ ] } ], + "description": "Statping allows you to group multiple services with a unique name for better viewablity. You can reorder the groups to make your status page a little more organized if you have tons of services.", "auth": { "type": "bearer", "bearer": [ @@ -1541,7 +1434,8 @@ ] } } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Users", @@ -1554,6 +1448,10 @@ "script": { "id": "9a2977fe-9689-4039-bdcb-eaa34abee958", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View All Users\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.length).to.eql(1);", @@ -1580,10 +1478,6 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/users", "host": [ @@ -1602,10 +1496,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/users", "host": [ @@ -1647,6 +1537,10 @@ "script": { "id": "1913466d-83b2-4d5b-ac48-89c9abdd0c8d", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Create User\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", @@ -1703,6 +1597,10 @@ "script": { "id": "555b7ba4-bb36-4e86-a541-fa5a5008f951", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View User\", function () {", " var jsonData = pm.response.json();", " var id = pm.globals.get(\"user_id\");", @@ -1729,10 +1627,6 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/users/{{user_id}}", "host": [ @@ -1751,10 +1645,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/users/{{user_id}}", "host": [ @@ -1807,6 +1697,10 @@ "script": { "id": "b0d22bbd-a428-4df3-8295-b40542bfa21f", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Update User\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", @@ -1848,7 +1742,8 @@ "users", "{{user_id}}" ] - } + }, + "description": "Update a user's information including username, email, password, and if they are an admin or not." }, "response": [ { @@ -1907,6 +1802,10 @@ "script": { "id": "bd8c3425-a97f-4f8c-b849-71b65dd543ee", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Delete User\", function () {", " var jsonData = pm.response.json();", " var id = pm.globals.get(\"user_id\");", @@ -1933,10 +1832,6 @@ }, "method": "DELETE", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/users/{{user_id}}", "host": [ @@ -1947,7 +1842,8 @@ "users", "{{user_id}}" ] - } + }, + "description": "Delete a specific user from the database." }, "response": [ { @@ -1955,10 +1851,6 @@ "originalRequest": { "method": "DELETE", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/users/{{user_id}}", "host": [ @@ -1994,6 +1886,7 @@ ] } ], + "description": "You can create new users with different permissions to allow for read only API access, and super admin abilities. ", "auth": { "type": "bearer", "bearer": [ @@ -2025,7 +1918,8 @@ ] } } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Notifiers", @@ -2038,6 +1932,10 @@ "script": { "id": "e9105618-6db8-4a57-ae7f-782989842f4a", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View All Notifiers\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.length).to.eql(9);", @@ -2060,10 +1958,6 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/notifiers", "host": [ @@ -2073,12 +1967,27 @@ "api", "notifiers" ] - } + }, + "description": "View an array of all the Notifiers including all details about the notifier." }, "response": [] }, { "name": "View Notifier", + "event": [ + { + "listen": "test", + "script": { + "id": "acac30b6-3caa-46c9-89be-1efbadf89f22", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -2092,10 +2001,6 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/notifier/mobile", "host": [ @@ -2106,7 +2011,8 @@ "notifier", "mobile" ] - } + }, + "description": "View a specific notifier and it's details." }, "response": [] }, @@ -2118,6 +2024,10 @@ "script": { "id": "d714d71d-4d6a-4b2e-a6ea-16c34dec3041", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Update Notifier\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.output.method).to.eql(\"mobile\");", @@ -2164,11 +2074,13 @@ "notifier", "mobile" ] - } + }, + "description": "Update a notifier to change it's values." }, "response": [] } ], + "description": "Statping contains multiple notifiers that will send you a notification whenever a service become offline, or online. You can create your own 3rd party notifier by reading more on the [Notifiers Wiki](https://github.com/hunterlong/statping/wiki/Notifiers) on the Github repo.", "auth": { "type": "bearer", "bearer": [ @@ -2178,7 +2090,30 @@ "type": "string" } ] - } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "bd5d8232-7ed7-4607-ab7c-14de85c3a033", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "ef420ba0-37dc-4ffc-b38f-a43ccca76606", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} }, { "name": "Messages", @@ -2191,6 +2126,10 @@ "script": { "id": "3c484d1b-6e77-4084-b844-3ca77dc50108", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View All Messages\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.length).to.be.at.least(2);", @@ -2203,10 +2142,6 @@ "request": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/messages", "host": [ @@ -2216,7 +2151,8 @@ "api", "messages" ] - } + }, + "description": "View an array of all messages inserted into the database." }, "response": [ { @@ -2224,10 +2160,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/messages", "host": [ @@ -2269,6 +2201,10 @@ "script": { "id": "12caf74a-61d7-4f6e-89b5-fca2f65464c4", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Create Message\", function () {", " var jsonData = pm.response.json();", " var object = jsonData.output;", @@ -2307,7 +2243,8 @@ "api", "messages" ] - } + }, + "description": "Create a new message show show on index page, or on a service." }, "response": [ { @@ -2367,6 +2304,10 @@ "script": { "id": "c30cc333-53f4-4e9a-9c32-958c905ec163", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View Message\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.title).to.eql(\"API Message\");", @@ -2380,10 +2321,6 @@ "request": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/messages/{{message_id}}", "host": [ @@ -2394,7 +2331,8 @@ "messages", "{{message_id}}" ] - } + }, + "description": "View a specific message and it's details." }, "response": [ { @@ -2402,10 +2340,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/messages/{{message_id}}", "host": [ @@ -2448,6 +2382,10 @@ "script": { "id": "e9dd78cc-0f38-4516-bf82-38dd3451b2e7", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Update Message\", function () {", " var jsonData = pm.response.json();", " var object = jsonData.output;", @@ -2486,7 +2424,8 @@ "messages", "{{message_id}}" ] - } + }, + "description": "Update a specific message with new details." }, "response": [ { @@ -2547,6 +2486,10 @@ "script": { "id": "6cb2527f-41c2-4feb-9573-1e4d59efa116", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Delete Message\", function () {", " var jsonData = pm.response.json();", " var id = pm.globals.get(\"message_id\");", @@ -2577,7 +2520,8 @@ "messages", "{{message_id}}" ] - } + }, + "description": "Delete a specific message from the database." }, "response": [ { @@ -2623,11 +2567,115 @@ } ] } - ] + ], + "description": "Messages allows you to create an alert for a service for a scheduled downtime, or a simple message letting users know a service might be having internal issues. ", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "8d24599b-f157-475b-9e30-06c1171c85bc", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "497c2428-fe8a-42c2-bf6f-8f2cdf959771", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} }, { "name": "Checkins", "item": [ + { + "name": "View All Checkin's", + "event": [ + { + "listen": "test", + "script": { + "id": "54bded63-de27-4839-8783-25874c35c3ea", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"View All Checkins\", function () {", + " var jsonData = pm.response.json();", + " var first = jsonData[0];", + " var id = pm.globals.get(\"checkin_id\");", + " pm.expect(first.name).to.eql(\"Server Checkin\");", + " pm.expect(first.api_key).to.eql(id);", + " pm.expect(first.grace).to.eql(60);", + " pm.expect(first.interval).to.eql(900);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/checkins", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "checkins" + ] + }, + "description": "View an array of all the Checkin's inserted into the system." + }, + "response": [ + { + "name": "View All Checkin's", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/checkins", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "checkins" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Mon, 10 Dec 2018 19:34:16 GMT" + }, + { + "key": "Content-Length", + "value": "268" + } + ], + "cookie": [], + "body": "[\n {\n \"id\": 5,\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60,\n \"api_key\": \"1emn9ha\",\n \"created_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"updated_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"failing\": false,\n \"last_hit\": \"0001-01-01T00:00:00Z\",\n \"hits\": [],\n \"failures\": null\n }\n]" + } + ] + }, { "name": "Create Checkin", "event": [ @@ -2636,6 +2684,10 @@ "script": { "id": "af07a95b-1bf5-42c7-bd3f-a32f3ab2a264", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Create Checkin\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", @@ -2674,7 +2726,8 @@ "api", "checkin" ] - } + }, + "description": "Create a new Checkin." }, "response": [ { @@ -2726,89 +2779,6 @@ } ] }, - { - "name": "View All Checkin's", - "event": [ - { - "listen": "test", - "script": { - "id": "54bded63-de27-4839-8783-25874c35c3ea", - "exec": [ - "pm.test(\"View All Checkins\", function () {", - " var jsonData = pm.response.json();", - " var first = jsonData[0];", - " var id = pm.globals.get(\"checkin_id\");", - " pm.expect(first.name).to.eql(\"Server Checkin\");", - " pm.expect(first.api_key).to.eql(id);", - " pm.expect(first.grace).to.eql(60);", - " pm.expect(first.interval).to.eql(900);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{endpoint}}/api/checkins", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "checkins" - ] - } - }, - "response": [ - { - "name": "View All Checkin's", - "originalRequest": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{endpoint}}/api/checkins", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "checkins" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Date", - "value": "Mon, 10 Dec 2018 19:34:16 GMT" - }, - { - "key": "Content-Length", - "value": "268" - } - ], - "cookie": [], - "body": "[\n {\n \"id\": 5,\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60,\n \"api_key\": \"1emn9ha\",\n \"created_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"updated_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"failing\": false,\n \"last_hit\": \"0001-01-01T00:00:00Z\",\n \"hits\": [],\n \"failures\": null\n }\n]" - } - ] - }, { "name": "Run Checkin", "event": [ @@ -2817,6 +2787,10 @@ "script": { "id": "652a1c5b-5379-43c2-9cd8-ebd8f8dad0f6", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Hit the Checkin API Endpoint\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", @@ -2831,10 +2805,6 @@ "request": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/checkin/{{checkin_id}}", "host": [ @@ -2844,7 +2814,8 @@ "checkin", "{{checkin_id}}" ] - } + }, + "description": "Initiate the Checkin request to your Statping instance. This request will come from your service and will trigger a failure if your service does not hit this URL in the routine interval set.\n\n- `checkin_id` is the ID for the checkin" }, "response": [ { @@ -2852,10 +2823,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/checkin/{{checkin_id}}", "host": [ @@ -2897,6 +2864,10 @@ "script": { "id": "93ff6b86-368b-406f-9d75-07338714ebca", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"View Checkin\", function () {", " var jsonData = pm.response.json();", " var id = pm.globals.get(\"checkin_id\");", @@ -2913,10 +2884,6 @@ "request": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/checkin/{{checkin_id}}", "host": [ @@ -2927,7 +2894,8 @@ "checkin", "{{checkin_id}}" ] - } + }, + "description": "View a specific checkin and it's details." }, "response": [ { @@ -2935,10 +2903,6 @@ "originalRequest": { "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{endpoint}}/api/checkin/{{checkin_id}}", "host": [ @@ -2981,6 +2945,10 @@ "script": { "id": "d3fdc6b9-d8ca-4634-a735-af8db0f31cef", "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", "pm.test(\"Delete Checkin\", function () {", " var jsonData = pm.response.json();", " var id = pm.globals.get(\"checkin_id\");", @@ -3011,7 +2979,8 @@ "checkin", "{{checkin_id}}" ] - } + }, + "description": "Delete a checkin from database and stop failures from service not hitting the checkin URL." }, "response": [ { @@ -3057,7 +3026,238 @@ } ] } - ] + ], + "description": "A Checkin is when a service communicates to your Statping instance rather than Statping attempting to hit your service. Checkin's have a scheduled interval that waits for your service to notify Statping, if your service does not hit the Checkin URL during the interval you set, it will result in a service failing notification.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "fefe9fc9-8020-41ec-b20f-dd2c5ad951e9", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "4a611b52-8ec2-4e26-8ed8-51f2729f93a1", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Miscellaneous", + "item": [ + { + "name": "Statping Details", + "event": [ + { + "listen": "test", + "script": { + "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Check Core API Route\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.description).to.eql(\"Statping Monitoring Sample Data\");", + " pm.expect(jsonData.using_cdn).to.eql(false);", + " pm.expect(jsonData.database).to.eql(\"sqlite\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api" + ] + }, + "description": "The root API endpoint to view basic Statping configuration including Name, URL, database type, and other useful fields." + }, + "response": [] + }, + { + "name": "Statping Clear Cache", + "event": [ + { + "listen": "test", + "script": { + "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/clear_cache", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "clear_cache" + ] + }, + "description": "This endpoint will clear all the cache files in your Statping instance. This includes chart data and service views." + }, + "response": [] + }, + { + "name": "Statping Reset API Tokens", + "event": [ + { + "listen": "test", + "script": { + "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/renew", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "renew" + ] + }, + "description": "Reset your root API Key and Secret values to brand new values." + }, + "response": [] + } + ], + "description": "This is for Statping's miscellaneous API endpoints that aren't a part of another category.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "883519e8-7c7d-49c0-9812-d988d0179907", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "7a0738e6-2fc4-45cb-9f1a-1cd57fb76b66", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} } - ] + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "4eb22861-cca3-4592-a1a4-4d01bc62ee74", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "cac1a6f2-2dc3-4b09-9849-3d5aab919244", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} } \ No newline at end of file From 59011277edac3b3c84aa4b7f69067e866ffb6ca8 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 22 Nov 2019 10:48:34 -0800 Subject: [PATCH 050/100] go modules --- go.mod | 2 +- source/source.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 69a38252..e7f9b803 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/joho/godotenv v1.3.0 github.com/lib/pq v1.2.0 // indirect github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f - github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/russross/blackfriday/v2 v2.0.1 github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/stretchr/testify v1.4.0 github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e diff --git a/source/source.go b/source/source.go index 9bb81a56..0d917140 100644 --- a/source/source.go +++ b/source/source.go @@ -22,7 +22,7 @@ import ( "fmt" "github.com/GeertJohan/go.rice" "github.com/hunterlong/statping/utils" - "gopkg.in/russross/blackfriday.v2" + "github.com/russross/blackfriday/v2" "io/ioutil" "os" ) From 97b30c2418b814f91940b2e01b47c85e930217f4 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 22 Nov 2019 11:01:58 -0800 Subject: [PATCH 051/100] go modules --- Makefile | 3 +-- go.mod | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 876d0ddf..8f560f62 100644 --- a/Makefile +++ b/Makefile @@ -218,8 +218,7 @@ dev-deps: $(GOGET) github.com/GeertJohan/go.rice/rice $(GOINSTALL) github.com/GeertJohan/go.rice/rice $(GOCMD) get github.com/axw/gocov/gocov - $(GOCMD) get gopkg.in/matm/v1/gocov-html - $(GOCMD) install gopkg.in/matm/v1/gocov-html + $(GOCMD) get github.com/matm/gocov-html $(GOCMD) get github.com/mgechev/revive $(GOCMD) get github.com/fatih/structs $(GOGET) github.com/ararog/timeago diff --git a/go.mod b/go.mod index e7f9b803..196a896b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/99designs/gqlgen v0.10.1 github.com/GeertJohan/go.rice v1.0.0 github.com/agnivade/levenshtein v1.0.2 // indirect - github.com/ararog/timeago v0.0.0-20160324182854-518814407569 + github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d github.com/daaku/go.zipexe v1.0.1 // indirect github.com/go-mail/mail v2.3.1+incompatible github.com/go-yaml/yaml v2.1.0+incompatible @@ -23,12 +23,12 @@ require ( github.com/stretchr/testify v1.4.0 github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e github.com/vektah/gqlparser v1.1.2 - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect google.golang.org/appengine v1.6.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.2.7 // indirect ) From 89a69927a9704cc24c05f0e330c7b666f287fd7c Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 22 Nov 2019 11:08:12 -0800 Subject: [PATCH 052/100] go mod --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bbf9244d..94ac1a4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,8 +50,8 @@ before_install: install: - npm install -g sass - npm install -g newman + - go mod vendor - make dev-deps - - make dep - make install before_script: From dd2f3bed66b8d9c5b5beca52d1e4ebc1d2eb456f Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 22 Nov 2019 11:20:19 -0800 Subject: [PATCH 053/100] go modules --- Dockerfile | 4 +--- dev/Dockerfile | 6 ++---- source/wiki.go | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index b7f9677a..c0a9751b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,11 @@ LABEL maintainer="Hunter Long (https://github.com/hunterlong)" ARG VERSION ENV DEP_VERSION v0.5.0 RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass -RUN curl -L -s https://github.com/golang/dep/releases/download/$DEP_VERSION/dep-linux-amd64 -o /go/bin/dep && \ - chmod +x /go/bin/dep RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \ chmod +x /usr/local/bin/sass WORKDIR /go/src/github.com/hunterlong/statping ADD Makefile Gopkg.* /go/src/github.com/hunterlong/statping/ -RUN make dep && \ +RUN go mod vendor && \ make dev-deps ADD . /go/src/github.com/hunterlong/statping RUN make install diff --git a/dev/Dockerfile b/dev/Dockerfile index 380749cb..26ff33a1 100644 --- a/dev/Dockerfile +++ b/dev/Dockerfile @@ -3,13 +3,11 @@ MAINTAINER "Hunter Long (https://github.com/hunterlong)" ARG VERSION ENV DEP_VERSION v0.5.0 RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq -RUN curl -L -s https://github.com/golang/dep/releases/download/$DEP_VERSION/dep-linux-amd64 -o /go/bin/dep && \ - chmod +x /go/bin/dep RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \ chmod +x /usr/local/bin/sass WORKDIR /go/src/github.com/hunterlong/statping ADD . /go/src/github.com/hunterlong/statping -RUN make dep +RUN go mod vendor RUN make dev-deps RUN make install @@ -17,4 +15,4 @@ ENV IS_DOCKER=true ENV STATPING_DIR=/app WORKDIR /app -CMD ["statping"] \ No newline at end of file +CMD ["statping"] diff --git a/source/wiki.go b/source/wiki.go index 6a68081f..af55c46b 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,9 +1,9 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-11-05 19:03:57.719603 -0800 PST m=+1.160734429 +// 2019-11-22 11:19:08.753313 -0800 PST m=+0.567340112 // // This contains the most recently Markdown source for the Statping Wiki. package source // CompiledWiki contains all of the Statping Wiki pages from the Github Wiki repo. -var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Upgrading Staping\nYou can upgrade the Statping executable by running the commands below on your EC2 instance.\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-x64.tar.gz\ntar -xvzf statping-linux-x64.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n```\nYou can test the version number by running `statping version`.\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dep\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n# Adding Notifiers\nTo add a notifier to the Statping application, simply append your Notifier in the `AttachNotifiers()` function inside of [core/core.go](https://github.com/hunterlong/statping/blob/master/core/core.go).\n\n```go\n// AttachNotifiers will attach all the notifier's into the system\nfunc AttachNotifiers() error {\n\treturn notifier.AddNotifiers(\n\t\tnotifiers.Command,\n\t\tnotifiers.Discorder,\n\t\tnotifiers.Emailer,\n\t\tnotifiers.LineNotify,\n\t\tnotifiers.Mobile,\n\t\tnotifiers.Slacker,\n\t\tnotifiers.Telegram,\n\t\tnotifiers.Twilio,\n\t\tnotifiers.Webhook,\n\t)\n}\n```\n###### [AttachNotifiers](https://github.com/hunterlong/statping/blob/master/core/core.go#L183)\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSLMODE=false # enable ssl_mode for postgres (To enable use require)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") +var CompiledWiki = []byte("Types of Monitoring
Features
Start Statping
Linux
Mac
Windows
AWS EC2
Docker
Mobile App
Heroku
API
Makefile
Notifiers
Notifier Events
Notifier Example
Prometheus Exporter
SSL
Config with .env File
Static Export
Statping Plugins
Statuper
Build and Test
Contributing
PGP Signature
Testing
Deployment
\n\n

Types of Monitoring

\nYou can monitor your application by using a simple HTTP GET to the endpoint to return back a response and status code. Normally you want a 200 status code on an HTTP request. You might want to require a 404 or 500 error as a response code though. With each service you can include a Timeout in seconds to work with your long running services.\n\n# HTTP Endpoints with Custom POST\nFor more advanced monitoring you can add a data as a HTTP POST request. This is useful for automatically submitting JSON, or making sure your signup form is working correctly.\n\n

\n\n

\n\nWith a HTTP service, you can POST a JSON string to your endpoint to retrieve any type of response back. You can then use Regex in the Expected Response field to parse a custom response that exactly matches your status requirements. \n\n# TCP/UDP Services\nFor other services that don't use HTTP, you can monitor any type of service by using the PORT of the service. If you're Ethereum Blockchain server is running on 8545, you can use TCP to monitor your server. With a TCP service, you can monitor your Docker containers, or remove service running on a custom port. You don't need to include `http` in the endpoint field, just IP or Hostname.\n\n

\n\n

\n\n# ICMP Service\nYou can send a [ICMP](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) (ping) to an endpoint rather than HTTP/TCP/UDP request for a quick response.\n\n\n

Features

\nStatping is a great Status Page that can be deployed with 0 effort.\n\n# 3 Different Databases\nYou can use MySQL, Postgres, or SQLite as a database for your Statping status page. The server will automatically upgrade your database tables depending on which database you have.\n\n# Easy to Startup\nStatping is an extremely easy to setup website monitoring tool without fussing with dependencies or packages. Simply download and install the precompile binary for your operating system. Statping works on Windows, Mac, Linux, Docker, and even the Raspberry Pi.\n\n# Plugins\nStatping is an awesome Status Page generator that allows you to create your own plugins with Golang Plugins! You don't need to request a PR or even tell us about your plugin. Plugin's are compiled and then send as a binary to the Statping `/plugins` folder. Test your plugins using the `statup test plugin` command, checkout the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about creating plugins.\n\n# No Maintenance\nMany other website monitoring applications will collect data until the server fails because of hard drive is 100% full. Statping will automatically delete records to make sure your server will stay UP for years. The EC2 AMI Image is a great way to host your status page without worrying about it crashing one day. Statping will automatically upgrade its software when you reboot your computer.\n\n# Email & Slack Notifications\nReceive email notifications if your website or application goes offline. Statping includes SMTP connections so you can use AWS SES, or any other SMTP emailing service. Go in the Email Settings in Settings to configure these options.\n\n# Prometheus Exporter\nIf you want a deeper view of your applications status, you can use Grafana and Prometheus to graph all types of data about your services. Read more about the [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter)\n\n

Start Statping

\n\n\n

Linux

\n# Installing on Linux\nInstalling Statping on Linux can be done by downloading the latest tar.gz file, unzipping, and running the executable. You can also install using [Snapcraft](https://snapcraft.io/) for Ubuntu systems.\n\n1. Download the [Latest Version](https://github.com/hunterlong/statping/releases/latest) tar.gz file based on your architecture. \n\n2. Unzip the tar.gz file with command: `tar -xzf statping-linux-x64.tar.gz` (use the file name you downloaded)\n\n3. Enter the new directory and run `./statping` or you can move the executable file to the system with command: `mv statping /usr/local/bin/statping`.\n\n## Install using Snapcraft\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/statping)\n\nIf you are using [snap](https://snapcraft.io/statping), you can simply run this command to install Statping.\n```shell\nsudo snap install statping\n```\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Apt: `apt install ruby-sass -y`\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n## Systemd Service\nSetting up a systemd service is a great way to make sure your Statping server will automatically reboot when needed. You can use the file below for your service. You should have Statping already installed by this step.\n###### /etc/systemd/system/statping.service\n```\n[Unit]\nDescription=Statping Server\nAfter=network.target\nAfter=systemd-user-sessions.service\nAfter=network-online.target\n\n[Service]\nType=simple\nRestart=always\nExecStart=/usr/local/bin/statping\nWorkingDirectory=/usr/local/bin\n\n[Install]\nWantedBy=multi-user.target\n```\nThen you can enable and start your systemd service with:\n```\nsystemctl daemon-reload\n\nsystemctl enable statping.service\n\nsystemctl start statping\n```\nYou're Statping server will now automatically restart when your server restarts.\n\n## Raspberry Pi\nYou can even run Statping on your Raspberry Pi by installing the precompiled binary from [Latest Releases](https://github.com/hunterlong/statping/releases/latest). For the Raspberry Pi 3 you'll want to download the `statping-linux-arm7.tar.gz` file. Be sure to change `VERSION` to the latest version in Releases, and include the 'v'.\n\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-arm7.tar.gz\ntar -xvzf statping-linux-arm7.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n\nstatping version\n``` \n\n## Alpine Linux\nThe Docker image is using the Statping Alpine binary since it's so incredibly small. You can run it on your own alpine image by downloading `statping-linux-alpine.tar.gz` from [Latest Releases](https://github.com/hunterlong/statping/releases/latest).\n\n

Mac

\n# Installing on Mac\nStatping includes an easy to use [Homebrew Formula](https://github.com/hunterlong/homebrew-statping) to quick get your Status Page up and running locally. Statping on brew is automatically generated for each new release to master. Install with the commands below,\n```bash\nbrew tap hunterlong/statping\nbrew install statping\n```\n\n

\n\n

\n\nIf you don't have brew, then you can install it with this command below:\n```bash\nbash <(curl -s https://statping.com/install.sh)\n```\n\nOnce you've installed it, checkout which version you have by running `statping version`.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n\n

Windows

\n# Installing on Windows\nCurrently, Statping only works on Windows 64-bit computers. Just download the exe file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and run it in your command prompt. It will create a HTTP server on port 8080, so you can visit `http://localhost:8080` to see your Statping Status Page.\n\n# Compiling SCSS for Custom Theme\nStatping requires `sass` to be installed to the local machine to compile SCSS into CSS if you want to use the Custom Theme features. \n\n- Node: `npm install sass -g`\n- Ruby: `gem install sass`\n\n# Running Statping as a Service\nTo ensure Statping is always running, it can be installed to run as a service on a Windows machine. The easiest way to do that is by using NSSM, the [Non-Sucking Service Manager](https://nssm.cc/download). Download and unzip the compressed file to a location on your machine running Statping to get started:\n1. Open an administrative command prompt.\n2. Change to the folder that contains the 64 bit version of NSSM.\n3. Type \"nssm install Statping\" and press enter.\n4. For the properties, use the following as an example:\n Path: C:\\Program Files\\Statping\\statping.exe\n Startup directory: C:\\Program Files\\Statping\n5. Click \"Install\".\n6. Launch the windows services manager.\n7. Run Statping.\n\n## Known Issues with Windows\nUnfortunately, Statping only works on Windows 64-bit processors. If you have more than 4gb of ram, there's a good chance you already have a 64-bit processor. Download the [Latest Releases](https://github.com/hunterlong/statping/releases/latest) of Statping, extract the ZIP file, then double click on the `statping.exe` file. You can use a SQLite database for a quick setup, or connect to a local/remote Postgres or MySQL database server.\n\n

AWS EC2

\nRunning Statping on the smallest EC2 server is very quick using the AWS AMI Image. The AWS AMI Image will automatically start a Statping Docker container that will automatically update to the latest version. Once the EC2 is booted, you can go to the Public DNS domain to view the Statping installation page. The Statping root folder is located at: `/statping` on the server.\n\n# AMI Image\nChoose the correct AMI Image ID based on your AWS region.\n- us-east-1 `ami-09ccd23d9c7afba61` (Virginia)\n- us-east-2 `ami-0c6c9b714a501cdb3` (Ohio)\n- us-west-1 `ami-02159cc1fc701a77e` (California)\n- us-west-2 `ami-007c6990949f5ccee` (Oregon)\n- eu-central-1 `ami-06e252d6d8b0c2f1f` (Frankfurt)\n\n# Upgrading Staping\nYou can upgrade the Statping executable by running the commands below on your EC2 instance.\n```\nVERSION=$(curl -s \"https://github.com/hunterlong/statping/releases/latest\" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')\nwget https://github.com/hunterlong/statping/releases/download/$VERSION/statping-linux-x64.tar.gz\ntar -xvzf statping-linux-x64.tar.gz\nchmod +x statping\nmv statping /usr/local/bin/statping\n```\nYou can test the version number by running `statping version`.\n\n# Instructions\n\n### 1. Create an EC2 instance from AMI Image\nGo to the main EC2 dashboard and click 'Launch Instance'. Then type `Statping` inside the search field for 'Community AMI'. Once you've found it in your region, click Select!\n\n\n\n### 2. Get the Public DNS for EC2 Instance\nCopy the 'Public DNS' URL and paste it into your browser.\n\n\n\n### 3. Setup Statping\nUse SQLite if you don't want to connect to a remote MySQL or Postgres database.\n\n\n\n# EC2 Server Features\nRunning your Statping server on a small EC2 instance is perfect for most users. Below you'll find some commands to get up and running in seconds.\n- Super cheap on the t2.nano (~$4.60 monthly)\n- Small usage, 8gb of hard drive\n- Automatic SSL certificate if you require it\n- Automatic reboot when the server needs it\n- Automatic database cleanup, so you'll never be at 100% full.\n- Automatic docker containers/images removal\n\n## Create Security Groups\nUsing the AWS CLI you can copy and paste the commands below to auto create everything for you. The server opens port 80 and 443.\n```bash\naws ec2 create-security-group --group-name StatpingPublicHTTP --description \"Statping HTTP Server on port 80 and 443\"\n# will response back a Group ID. Copy ID and use it for --group-id below.\n```\n```bash\nGROUPS=sg-7e8b830f\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 80 --cidr 0.0.0.0/0\naws ec2 authorize-security-group-ingress --group-id $GROUPS --protocol tcp --port 443 --cidr 0.0.0.0/0\n```\n## Create EC2 without SSL\nOnce your server has started, go to the EC2 Public DNS endpoint. You should be redirected to /setup to continue your installation process! The database information is already inputed for you.\n```bash\nGROUPS=sg-7e8b830f\nKEY=MYKEYHERE\nAMI_IMAGE=ami-7be8a103\n\naws ec2 run-instances \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n## Create EC2 with Automatic SSL Certification\nStart a Statping server with an SSL cert that will automatically regenerate when it's near expiration time. You'll need to point your domain's A record (IP address) or CNAME (public DNS endpoint) to use this feature.\n\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/dev/ec2-ssl.sh\n```\n\n```bash\n# edit the contents inside of ec2-ssl.sh then continue\nLETSENCRYPT_HOST=\"status.MYDOMAIN.com\"\nLETSENCRYPT_EMAIL=\"noreply@MYEMAIL.com\"\n```\nEdit ec2-ssl.sh and insert your domain you want to use, then run command below. Use the Security Group ID that you used above for --security-group-ids\n```\nGROUPS=sg-7e8b830f\nAMI_IMAGE=ami-7be8a103\nKEY=MYKEYHERE\n\naws ec2 run-instances \\\n --user-data file://ec2-ssl.sh \\\n --image-id $AMI_IMAGE \\\n --count 1 --instance-type t2.nano \\\n --key-name $KEY \\\n --security-group-ids $GROUPS\n```\n\n### EC2 Server Specs\n- t2.nano ($4.60 monthly)\n- 8gb SSD Memory\n- 0.5gb RAM\n- Docker with Docker Compose installed\n- Running Statping, NGINX, and Postgres\n- boot scripts to automatically clean unused containers.\n\n\n\n

Docker

\nStatping is easily ran on Docker with the light weight Alpine linux image. View on [Docker Hub](https://hub.docker.com/r/hunterlong/statping).\n\n[![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)\n\n# Latest Docker Image\nThe `latest` Docker image uses Alpine Linux to keep it ultra small.\n```bash\ndocker run -d \\\n -p 8080:8080 \\\n --restart always \\\n hunterlong/statping\n```\n\n# Mounting Volume\nYou can mount a volume to the `/app` Statping directory. This folder will contain `logs`, `config.yml`, and static assets if you want to edit the SCSS/CSS. \n```bash\ndocker run -d \\\n -p 8080:8080 \\\n -v /mydir/statping:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Attach a SSL Certificate\nWhen you mount `server.crt` and `server.key` to the `/app` directory, Statping will run a HTTPS server on port 443. Checkout the [SSL Wiki](https://github.com/hunterlong/statping/wiki/SSL) documentation to see more information about this.\n```bash\ndocker run -d \\\n -p 443:443 \\\n -v /mydir/domain.crt:/app/server.crt \\\n -v /mydir/domain.key:/app/server.key \\\n -v /mydir:/app \\\n --restart always \\\n hunterlong/statping\n```\n\n# Development Docker Image\nIf you want to run Statping that was build from the source, use the `dev` Docker image.\n```bash\ndocker run -d -p 8080:8080 hunterlong/statping:dev\n```\n\n# Cypress Testing Docker Image\nThis Docker image will pull the latest version of Statping and test the web interface with [Cypress](https://www.cypress.io/).\n```bash\ndocker run -it -p 8080:8080 hunterlong/statping:cypress\n```\n\n#### Or use Docker Compose\nThis Docker Compose file inlcudes NGINX, Postgres, and Statping.\n\n### Docker Compose with NGINX and Postgres\nOnce you initiate the `docker-compose.yml` file below go to http://localhost and you'll be forwarded to the /setup page. \nDatabase Authentication\n- database: `postgres`\n- port: `5432`\n- username: `statup`\n- password: `password123`\n- database: `statup`\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: localhost\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: localhost\n VIRTUAL_PORT: 8080\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: EC2 Example\n DESCRIPTION: This is a Statping Docker Compose instance\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\nOr a simple wget...\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose.yml\ndocker-compose up -d\n```\n\n#### Docker Compose with Automatic SSL\nYou can automatically start a Statping server with automatic SSL encryption using this docker-compose file. First point your domain's DNS to the Statping server, and then run this docker-compose command with DOMAIN and EMAIL. Email is for letsencrypt services.\n```bash\nwget https://raw.githubusercontent.com/hunterlong/statping/master/servers/docker-compose-ssl.yml\n\nLETSENCRYPT_HOST=mydomain.com \\\n LETSENCRYPT_EMAIL=info@mydomain.com \\\n docker-compose -f docker-compose-ssl.yml up -d\n```\n\n### Full docker-compose with Automatic SSL\n\n```yaml\nversion: '2.3'\n\nservices:\n\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs:ro\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html:ro\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: ${LETSENCRYPT_HOST}\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statup/nginx/certs:/etc/nginx/certs\n - ./statup/nginx/vhost:/etc/nginx/vhost.d\n - ./statup/nginx/html:/usr/share/nginx/html\n - ./statup/nginx/dhparam:/etc/nginx/dhparam\n\n statup:\n container_name: statup\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n - database\n depends_on:\n - postgres\n volumes:\n - ./statup/app:/app\n environment:\n VIRTUAL_HOST: ${LETSENCRYPT_HOST}\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}\n LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}\n DB_CONN: postgres\n DB_HOST: postgres\n DB_USER: statup\n DB_PASS: password123\n DB_DATABASE: statup\n NAME: SSL Example\n DESCRIPTION: This Status Status Page should be running ${LETSENCRYPT_HOST} with SSL.\n\n postgres:\n container_name: postgres\n image: postgres\n restart: always\n networks:\n - database\n volumes:\n - ./statup/postgres:/var/lib/postgresql/data\n environment:\n POSTGRES_PASSWORD: password123\n POSTGRES_USER: statup\n POSTGRES_DB: statup\n\nnetworks:\n internet:\n driver: bridge\n database:\n driver: bridge\n```\n\n

Mobile App

\nStatping has a free mobile app so you can monitor your websites and applications without the need of a computer. \n\n![iTunes App Store](https://img.shields.io/itunes/v/1445513219.svg)\n\n

\n\n\n

\n\n

\n\n

\n\n\n

Heroku

\nYou can now instantly deploy your Statping instance on a free Heroku container. Simply click the deploy button below and get up in running within seconds. This Heroku deployment is based on the Statping Docker image so you will have all the great features including SASS and all the notifiers without any setup. \n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hunterlong/statping/tree/master)\n\nView the live Heroku Statping instance at: [https://statping.herokuapp.com](https://statping.herokuapp.com)\n\n# Database Configuration\nYou will need to deploy a Postgres database to your instance and insert some configuration variables. View the image below to see what environment variable you need to configure. If you insert `DB_CONN`, Statping will attempt to automatically connect to the database without the need for the `config.yml` file. \n\n![](https://img.cjx.io/herokustatping.png)\n\n\n

API

\nStatping includes a RESTFUL API so you can view, update, and edit your services with easy to use routes. You can currently view, update and delete services, view, create, update users, and get detailed information about the Statping instance. To make life easy, try out a Postman or Swagger JSON file and use it on your Statping Server.\n\n

\nPostman | Postman JSON Export | Swagger Export\n

\n\n## Authentication\nAuthentication uses the Statping API Secret to accept remote requests. You can find the API Secret in the Settings page of your Statping server. To send requests to your Statping API, include a Authorization Header when you send the request. The API will accept any one of the headers below.\n\n- HTTP Header: `Authorization: API SECRET HERE`\n- HTTP Header: `Authorization: Bearer API SECRET HERE`\n\n## Main Route `/api`\nThe main API route will show you all services and failures along with them.\n\n## Services\nThe services API endpoint will show you detailed information about services and will allow you to edit/delete services with POST/DELETE http methods.\n\n### Viewing All Services\n- Endpoint: `/api/services`\n- Method: `GET`\n- Response: Array of [Services](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing Service\n- Endpoint: `/api/services/{id}`\n- Method: `GET`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Updating Service\n- Endpoint: `/api/services/{id}`\n- Method: `POST`\n- Response: [Service](https://github.com/hunterlong/statping/wiki/API#service-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}\n```\n\n### Deleting Service\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 4,\n \"type\": \"service\",\n \"method\": \"delete\"\n}\n```\n\n## Users\nThe users API endpoint will show you users that are registered inside your Statping instance.\n\n### View All Users\n- Endpoint: `/api/users`\n- Method: `GET`\n- Response: Array of [Users](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Viewing User\n- Endpoint: `/api/users/{id}`\n- Method: `GET`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\n### Creating New User\n- Endpoint: `/api/users`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"newadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Updating User\n- Endpoint: `/api/users/{id}`\n- Method: `POST`\n- Response: [User](https://github.com/hunterlong/statping/wiki/API#user-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nPOST Data:\n```json\n{\n \"username\": \"updatedadmin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}\n```\n\n### Deleting User\n- Endpoint: `/api/services/{id}`\n- Method: `DELETE`\n- Response: [Object Response](https://github.com/hunterlong/statping/wiki/API#object-response)\n- Response Type: `application/json`\n- Request Type: `application/json`\n\nResponse:\n```json\n{\n \"status\": \"success\",\n \"id\": 3,\n \"type\": \"user\",\n \"method\": \"delete\"\n}\n```\n\n# Service Response\n```json\n{\n \"id\": 8,\n \"name\": \"Test Service 0\",\n \"domain\": \"https://status.coinapp.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 1,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:07:03.045832088-07:00\",\n \"updated_at\": \"2018-09-12T09:07:03.046114305-07:00\",\n \"online\": false,\n \"latency\": 0.031411064,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 502,\n \"last_online\": \"0001-01-01T00:00:00Z\",\n \"dns_lookup_time\": 0.001727175,\n \"failures\": [\n {\n \"id\": 5187,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:46.292277471-07:00\"\n },\n {\n \"id\": 5188,\n \"issue\": \"HTTP Status Code 502 did not match 200\",\n \"created_at\": \"2018-09-12T10:41:47.337659862-07:00\"\n }\n ]\n}\n```\n\n# User Response\n```json\n{\n \"id\": 1,\n \"username\": \"admin\",\n \"api_key\": \"02f324450a631980121e8fd6ea7dfe4a7c685a2f\",\n \"admin\": true,\n \"created_at\": \"2018-09-12T09:06:53.906398511-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.972440207-07:00\"\n}\n```\n\n# Object Response\n```json\n{\n \"type\": \"service\",\n \"id\": 19,\n \"method\": \"delete\",\n \"status\": \"success\"\n}\n```\n\n# Main API Response\n```json\n{\n \"name\": \"Awesome Status\",\n \"description\": \"An awesome status page by Statping\",\n \"footer\": \"This is my custom footer\",\n \"domain\": \"https://demo.statping.com\",\n \"version\": \"v0.56\",\n \"migration_id\": 1536768413,\n \"created_at\": \"2018-09-12T09:06:53.905374829-07:00\",\n \"updated_at\": \"2018-09-12T09:07:01.654201225-07:00\",\n \"database\": \"sqlite\",\n \"started_on\": \"2018-09-12T10:43:07.760729349-07:00\",\n \"services\": [\n {\n \"id\": 1,\n \"name\": \"Google\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 10,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"created_at\": \"2018-09-12T09:06:54.97549122-07:00\",\n \"updated_at\": \"2018-09-12T09:06:54.975624103-07:00\",\n \"online\": true,\n \"latency\": 0.09080986,\n \"24_hours_online\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_online\": \"2018-09-12T10:44:07.931990439-07:00\",\n \"dns_lookup_time\": 0.005543935\n }\n ]\n}\n```\n\n\n

Makefile

\nHere's a simple list of Makefile commands you can run using `make`. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) may change often, so i'll try to keep this Wiki up-to-date.\n\n- Ubuntu `apt-get install build-essential`\n- MacOSX `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer`\n- Windows [Install Guide for GNU make utility](http://gnuwin32.sourceforge.net/packages/make.htm)\n- CentOS/RedHat `yum groupinstall \"Development Tools\"`\n\n### Commands\n```bash\nmake build # build the binary\nmake install\nmake run\nmake test\nmake coverage\nmake docs\n# Building Statping\nmake build-all\nmake build-alpine\nmake docker\nmake docker-run\nmake docker-dev\nmake docker-run-dev\nmake databases\nmake dev-deps\nmake clean\nmake compress\nmake cypress-install\nmake cypress-test\n```\n\n

Notifiers

\n

\n\n

\n\nStatping includes multiple Notifiers to alert you when your services are offline. You can also create your own notifier and send a Push Request to this repo! Creating a custom notifier is pretty easy as long as you follow the requirements. A notifier will automatically be installed into the users Statping database, and form values will save without any hassles. 💃\n\n

\nExample Code | Events | View Notifiers
\n\n

\n\n## Notifier Requirements\n- Must have a unique `METHOD` name\n- Struct must have `*notifier.Notification` pointer in it. \n- Must create and add your notifier variable in `init()`\n- Should have a form for user to input their variables/keys. `Form: []notifier.NotificationForm`\n\n## Notifier Interface (required)\nStatping has the `Notifier` interface which you'll need to include in your notifier. Statping includes many other events/triggers for your notifier, checkout Notifier Events to see all of them.\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\tOnSave() error // OnSave is triggered when the notifier is saved\n\tSend(interface{}) error // OnSave is triggered when the notifier is saved\n\tSelect() *Notification // Select returns the *Notification for a notifier\n}\n```\n\n### Basic Interface (required)\nInclude `OnSuccess` and `OnFailure` to receive events when a service is online or offline.\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n### Test Interface\nThe OnTest method will give the front end user the ability to test your notifier without saving, the OnTest method for your notifier run the functionality to test the user's submitted parameters and respond an error if notifier is not correctly setup.\n```go\n// Tester interface will include a function to Test users settings before saving\ntype Tester interface {\n\tOnTest() error\n}\n```\nIf your notifier includes this interface, the Test button will appear.\n\n## Notifier Struct\n```go\nvar example = &Example{¬ifier.Notification{\n\tMethod: \"example\", // unique method name\n\tHost: \"http://exmaplehost.com\", // default 'host' field\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\", // text, password, number, or email\n\t\tTitle: \"Host\", // The title of value in form\n\t\tPlaceholder: \"Insert your Host here.\", // Optional placeholder in input\n\t\tDbField: \"host\", // An accepted DbField value (read below)\n\t}},\n}\n```\n\n## Notifier Form\nInclude a form with your notifier so other users can save API keys, username, passwords, and other values. \n```go\n// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.\ntype NotificationForm struct {\n\tType string `json:\"type\"` // the html input type (text, password, email)\n\tTitle string `json:\"title\"` // include a title for ease of use\n\tPlaceholder string `json:\"placeholder\"` // add a placeholder for the input\n\tDbField string `json:\"field\"` // true variable key for input\n\tSmallText string `json:\"small_text\"` // insert small text under a html input\n\tRequired bool `json:\"required\"` // require this input on the html form\n\tIsHidden bool `json:\"hidden\"` // hide this form element from end user\n\tIsList bool `json:\"list\"` // make this form element a comma separated list\n\tIsSwitch bool `json:\"switch\"` // make the notifier a boolean true/false switch\n}\n```\n\n### Example Notifier Form\nThis is the Slack Notifier `Form` fields.\n```go\nForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Incoming webhooker Url\",\n\t\tPlaceholder: \"Insert your slack webhook URL here.\",\n\t\tSmallText: \"Incoming webhooker URL from slack Apps\",\n\t\tDbField: \"Host\",\n\t\tRequired: true,\n\t}}\n}\n```\n\n### Accepted DbField Values\nThe `notifier.NotificationForm` has a field called `DbField` which is the column to save the value into the database. Below are the acceptable DbField string names to include in your form. \n- `host` used for a URL or API endpoint\n- `username` used for a username\n- `password` used for a password\n- `port` used for a integer port number\n- `api_key` used for some kind of API key\n- `api_secret` used for some API secret\n- `var1` used for any type of string\n- `var2` used for any type of string (extra)\n\n### Form Elements\nYou can completely custom your notifications to include a detailed form. \n- `Type` is a HTML input type for your field\n- `Title` give your input element a title\n- `Placeholder` optional field if you want a placeholder in input\n- `DbField` required field to save variable into database (read above)\n- `Placeholder` optional field for inserting small hint under the input\n\n# Adding Notifiers\nTo add a notifier to the Statping application, simply append your Notifier in the `AttachNotifiers()` function inside of [core/core.go](https://github.com/hunterlong/statping/blob/master/core/core.go).\n\n```go\n// AttachNotifiers will attach all the notifier's into the system\nfunc AttachNotifiers() error {\n\treturn notifier.AddNotifiers(\n\t\tnotifiers.Command,\n\t\tnotifiers.Discorder,\n\t\tnotifiers.Emailer,\n\t\tnotifiers.LineNotify,\n\t\tnotifiers.Mobile,\n\t\tnotifiers.Slacker,\n\t\tnotifiers.Telegram,\n\t\tnotifiers.Twilio,\n\t\tnotifiers.Webhook,\n\t)\n}\n```\n###### [AttachNotifiers](https://github.com/hunterlong/statping/blob/master/core/core.go#L183)\n\n

Notifier Events

\nEvents are handled by added interfaces for the elements you want to monitor.\n\n## Required Notifier Interface\n```go\n// Notifier interface is required to create a new Notifier\ntype Notifier interface {\n\t// Run will trigger inside of the notifier when enabled\n\tRun() error\n\t// OnSave is triggered when the notifier is saved\n\tOnSave() error\n\t// Test will run a function inside the notifier to Test if it works\n\tTest() error\n\t// Select returns the *Notification for a notifier\n\tSelect() *Notification\n}\n```\n\n## Basic Success/Failure Interface\n```go\n// BasicEvents includes the most minimal events, failing and successful service triggers\ntype BasicEvents interface {\n\t// OnSuccess is triggered when a service is successful\n\tOnSuccess(*types.Service)\n\t// OnFailure is triggered when a service is failing\n\tOnFailure(*types.Service, *types.Failure)\n}\n```\n\n\n## Service Events\n```go\n// ServiceEvents are events for Services\ntype ServiceEvents interface {\n\tOnNewService(*types.Service)\n\tOnUpdatedService(*types.Service)\n\tOnDeletedService(*types.Service)\n}\n```\n\n## User Events\n```go\n// UserEvents are events for Users\ntype UserEvents interface {\n\tOnNewUser(*types.User)\n\tOnUpdatedUser(*types.User)\n\tOnDeletedUser(*types.User)\n}\n```\n\n## Core Events\n```go\n// CoreEvents are events for the main Core app\ntype CoreEvents interface {\n\tOnUpdatedCore(*types.Core)\n}\n```\n\n## Notifier Events\n```go\n// NotifierEvents are events for other Notifiers\ntype NotifierEvents interface {\n\tOnNewNotifier(*Notification)\n\tOnUpdatedNotifier(*Notification)\n}\n```\n\n

Notifier Example

\nBelow is a full example of a Statping notifier which will give you a good example of how to create your own. Insert your new notifier inside the `/notifiers` folder once your ready!\n\n```go\npackage notifiers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/hunterlong/statping/types\"\n \"github.com/hunterlong/statping/core/notifier\"\n\t\"time\"\n)\n\ntype Example struct {\n\t*notifier.Notification\n}\n\nvar example = &Example{¬ifier.Notification{\n\tMethod: METHOD,\n\tTitle: \"Example\",\n\tDescription: \"Example Notifier\",\n\tAuthor: \"Hunter Long\",\n\tAuthorUrl: \"https://github.com/hunterlong\",\n\tDelay: time.Duration(5 * time.Second),\n\tForm: []notifier.NotificationForm{{\n\t\tType: \"text\",\n\t\tTitle: \"Host\",\n\t\tPlaceholder: \"Insert your Host here.\",\n\t\tDbField: \"host\",\n\t\tSmallText: \"this is where you would put the host\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Username\",\n\t\tPlaceholder: \"Insert your Username here.\",\n\t\tDbField: \"username\",\n\t}, {\n\t\tType: \"password\",\n\t\tTitle: \"Password\",\n\t\tPlaceholder: \"Insert your Password here.\",\n\t\tDbField: \"password\",\n\t}, {\n\t\tType: \"number\",\n\t\tTitle: \"Port\",\n\t\tPlaceholder: \"Insert your Port here.\",\n\t\tDbField: \"port\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Key\",\n\t\tPlaceholder: \"Insert your API Key here\",\n\t\tDbField: \"api_key\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"API Secret\",\n\t\tPlaceholder: \"Insert your API Secret here\",\n\t\tDbField: \"api_secret\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var 1\",\n\t\tPlaceholder: \"Insert your Var1 here\",\n\t\tDbField: \"var1\",\n\t}, {\n\t\tType: \"text\",\n\t\tTitle: \"Var2\",\n\t\tPlaceholder: \"Var2 goes here\",\n\t\tDbField: \"var2\",\n\t}},\n}}\n\n// REQUIRED init() will install/load the notifier\nfunc init() {\n\tnotifier.AddNotifier(example)\n}\n\n// REQUIRED - Send is where you would put the action's of your notifier\nfunc (n *Example) Send(msg interface{}) error {\n\tmessage := msg.(string)\n\tfmt.Printf(\"i received this string: %v\\n\", message)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Select() *notifier.Notification {\n\treturn n.Notification\n}\n\n// REQUIRED\nfunc (n *Example) OnSave() error {\n\tmsg := fmt.Sprintf(\"received on save trigger\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED\nfunc (n *Example) Test() error {\n\tmsg := fmt.Sprintf(\"received a test trigger\\n\")\n\tn.AddQueue(msg)\n\treturn nil\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnSuccess(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a count trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// REQUIRED - BASIC EVENT\nfunc (n *Example) OnFailure(s *types.Service, f *types.Failure) {\n\tmsg := fmt.Sprintf(\"received a failure trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a new service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a update service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedService(s *types.Service) {\n\tmsg := fmt.Sprintf(\"received a delete service trigger for service: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a new user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a updated user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnDeletedUser(s *types.User) {\n\tmsg := fmt.Sprintf(\"received a deleted user trigger for user: %v\\n\", s.Username)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedCore(s *types.Core) {\n\tmsg := fmt.Sprintf(\"received a updated core trigger for core: %v\\n\", s.Name)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnNewNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a new notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n\n// OPTIONAL\nfunc (n *Example) OnUpdatedNotifier(s *Notification) {\n\tmsg := fmt.Sprintf(\"received a update notifier trigger for notifier: %v\\n\", s.Method)\n\tn.AddQueue(msg)\n}\n```\n\n\n

Prometheus Exporter

\nStatping includes a prometheus exporter so you can have even more monitoring power with your services. The prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.\n\n# Grafana Dashboard\nStatping has a [Grafana Dashboard](https://grafana.com/dashboards/6950) that you can quickly implement if you've added your Statping service to Prometheus. Import Dashboard ID: `6950` into your Grafana dashboard and watch the metrics come in!\n\n

\n\n## Basic Prometheus Exporter\nIf you have Statping and the Prometheus server in the same Docker network, you can use the yaml config below.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['statping:8080']\n```\n\n## Remote URL Prometheus Exporter\nThis exporter yaml below has `scheme: https`, which you can remove if you arn't using HTTPS.\n```yaml\nscrape_configs:\n - job_name: 'statping'\n scheme: https\n scrape_interval: 30s\n bearer_token: 'SECRET API KEY HERE'\n static_configs:\n - targets: ['status.mydomain.com']\n```\n\n### `/metrics` Output\n```\nstatping_total_failures 206\nstatping_total_services 4\nstatping_service_failures{id=\"1\" name=\"Google\"} 0\nstatping_service_latency{id=\"1\" name=\"Google\"} 12\nstatping_service_online{id=\"1\" name=\"Google\"} 1\nstatping_service_status_code{id=\"1\" name=\"Google\"} 200\nstatping_service_response_length{id=\"1\" name=\"Google\"} 10777\nstatping_service_failures{id=\"2\" name=\"Statping.com\"} 0\nstatping_service_latency{id=\"2\" name=\"Statping.com\"} 3\nstatping_service_online{id=\"2\" name=\"Statping.com\"} 1\nstatping_service_status_code{id=\"2\" name=\"Statping.com\"} 200\nstatping_service_response_length{id=\"2\" name=\"Statping.com\"} 2\n```\n\n

SSL

\nYou can run Statping with a valid certificate by including 2 files in the root directory. Although, I personally recommend using NGINX or Apache to serve the SSL and then have the webserver direct traffic to the Statping instance. This guide will show you how to implement SSL onto your Statping server with multiple options.\n\n## SSL Certificate with Statping\nTo run the Statping HTTP server in SSL mode, you must include 2 files in the root directory of your Statping application. The 2 files you must include are:\n- `server.crt` SSL Certificate File\n- `server.key` SSL Certificate Key File\n\nThe filenames and extensions must match the exact naming above. If these 2 files are found, Statping will automatically start the HTTP server in SSL mode using your certificates. You can also generate your own SSL certificates, but you will receive a \"ERR_CERT_AUTHORITY_INVALID\" error. To generate your own, follow the commands below:\n\n```shell\nopenssl req -new -sha256 -key server.key -out server.csr\nopenssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650\n```\nThis will generate a self signed certificate that you can use for your Statup instance. I recommend using a web server to do SSL termination for your server though.\n\n## Choose a Web Server or Environment\n\n**Choose the environment running the Statping instance.**\n- [Docker](#docker)\n- [NGINX](#nginx)\n- [Apache](#apache)\n\n## Docker\nDocker might be the easiest way to get up and running with a SSL certificate. Below is a `docker-compose.yml` file that will run NGINX, LetEncrypt, and Statping.\n\n1. Point your domain or subdomain to the IP address of the Docker server. This would be done on CloudFlare, Route53, or some other DNS provider.\n\n2. Replace the `docker-compose.yml` contents:\n- `MY.DOMAIN.COM` with the domain you want to use\n- `MY@EMAIL.COM` with your email address\n\n3. Run the docker container by running command `docker-compose up -d`. Give a little bit of time for LetEncrypt to automatically generate your SSL certificate.\n\n###### `docker-compose.yml`\n```yaml\nversion: '2.3'\nservices:\n nginx:\n container_name: nginx\n image: jwilder/nginx-proxy\n ports:\n - 0.0.0.0:80:80\n - 0.0.0.0:443:443\n labels:\n - \"com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy\"\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/tmp/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs:ro\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html:ro\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n environment:\n DEFAULT_HOST: MY.DOMAIN.COM\n\n letsencrypt:\n container_name: letsencrypt\n image: jrcs/letsencrypt-nginx-proxy-companion\n networks:\n - internet\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - ./statping/nginx/certs:/etc/nginx/certs\n - ./statping/nginx/vhost:/etc/nginx/vhost.d\n - ./statping/nginx/html:/usr/share/nginx/html\n - ./statping/nginx/dhparam:/etc/nginx/dhparam\n\n statping:\n container_name: statping\n image: hunterlong/statping:latest\n restart: always\n networks:\n - internet\n depends_on:\n - nginx\n volumes:\n - ./statping/app:/app\n environment:\n VIRTUAL_HOST: MY.DOMAIN.COM\n VIRTUAL_PORT: 8080\n LETSENCRYPT_HOST: MY.DOMAIN.COM\n LETSENCRYPT_EMAIL: MY@EMAIL.COM\n\nnetworks:\n internet:\n driver: bridge\n```\n\n## NGINX\nIf you already have a NGINX web server running, you just have to add a proxy pass and your SSL certs to the nginx config or as a vhost. By default Statping runs on port 8080, you can change this port by starting server with `statping -ip 127.0.0.1 -port 9595`.\n\n- Replace `/my/absolute/directory/for/cert/server.crt` with SSL certificate file.\n- Replace `/my/absolute/directory/for/key/server.key` with SSL key file.\n- Run `service nginx restart` and try out https on your domain.\n\n##### Tutorials\n- [NGINX Guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)\n- [How To Set Up Nginx Load Balancing with SSL Termination](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-load-balancing-with-ssl-termination)\n\n###### `/etc/nginx/nginx.conf`\n```\n#user nobody;\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n include mime.types;\n default_type application/octet-stream;\n send_timeout 1800;\n sendfile on;\n keepalive_timeout 6500;\n server {\n listen 80;\n server_name localhost;\n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n # HTTPS server\n \n server {\n listen 443;\n server_name localhost;\n \n ssl on;\n ssl_certificate /my/absolute/directory/for/cert/server.crt;\n ssl_certificate_key /my/absolute/directory/for/key/server.key;\n ssl_session_timeout 5m;\n \n ssl_protocols SSLv2 SSLv3 TLSv1;\n ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;\n ssl_prefer_server_ciphers on;\n \n location / {\n proxy_pass http://localhost:8080;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Client-Verify SUCCESS;\n proxy_set_header X-Client-DN $ssl_client_s_dn;\n proxy_set_header X-SSL-Subject $ssl_client_s_dn;\n proxy_set_header X-SSL-Issuer $ssl_client_i_dn;\n proxy_read_timeout 1800;\n proxy_connect_timeout 1800;\n }\n }\n}\n```\n\n## Apache\n\n

Config with .env File

\nIt may be useful to load your environment using a `.env` file in the root directory of your Statping server. The .env file will be automatically loaded on startup and will overwrite all values you have in config.yml.\n\nIf you have the `DB_CONN` environment variable set Statping will bypass all values in config.yml and will require you to have the other DB_* variables in place. You can pass in these environment variables without requiring a .env file.\n\n## `.env` File\n```bash\nDB_CONN=postgres\nDB_HOST=0.0.0.0\nDB_PORT=5432\nDB_USER=root\nDB_PASS=password123\nDB_DATABASE=root\n\nNAME=Demo\nDESCRIPTION=This is an awesome page\nDOMAIN=https://domain.com\nADMIN_USER=admin\nADMIN_PASSWORD=admin\nADMIN_EMAIL=info@admin.com\nUSE_CDN=true\nPOSTGRES_SSLMODE=false # enable ssl_mode for postgres (To enable use require)\nDISABLE_LOGS=false # disable logs from appearing and writing to disk\n\nIS_DOCKER=false\nIS_AWS=false\nSASS=/usr/local/bin/sass\nCMD_FILE=/bin/bash\n```\nThis .env file will include additional variables in the future, subscribe to this repo to keep up-to-date with changes and updates. \n\n

Static Export

\nIf you want to use Statping as a CLI application without running a server, you can export your status page to a static HTML.\nThis export tool is very useful for people who want to export their HTML and upload/commit it to Github Pages or an FTP server.\n```dash\nstatup export\n```\n###### Creates `index.html` in the current directory with CDN asset URL's. 💃 \n\n

Statping Plugins

\nSince Statping is built in Go Language we can use the [Go Plugin](https://golang.org/pkg/plugin/) feature to create dynamic plugins that run on load. Statping has an event anytime anything happens, you can create your own plugins and do any type of function. To implement your own ideas into Statping, use the plugin using the [statup/plugin](https://github.com/hunterlong/statping/blob/master/plugin/main.go) package.\n```\ngo get github.com/hunterlong/statping/plugin\n```\n\n## Example Plugin\nStart off with the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) that includes all the interfaces and some custom options for you to expand on. You can include any type of function in your own plugin!\n\n

\n\n

\n\n## Building Plugins\nPlugins don't need a push request and they can be private! You'll need to compile your plugin to the Golang `.so` binary format. Once you've built your plugin, insert it into the `plugins` folder in your Statping directory and reboot the application. Clone the [Example Statping Plugin](https://github.com/hunterlong/statping_plugin) repo and try to build it yourself!\n\n#### Build Requirements\n- You must have `main.go`\n- You must create the Plugin variable on `init()`\n\n```bash\ngit clone https://github.com/hunterlong/statping_plugin\ncd statup-plugin\ngo build -buildmode=plugin -o example.so\n```\n###### Insert `example.so` into the `plugins` directory and reload Statping\n\n## Testing Statping Plugins\nStatping includes a couple tools to help you on your Plugin journey, you can use `statup test plugins` command to test all plugins in your `/plugins` folder. This test will attempt to parse your plugin details, and then it will send events for your plugin to be fired.\n```\nstatup test plugins\n```\n

\n\n

\n\nYour plugin should be able to parse and receive events before distributing it. The test tools creates a temporary database (SQLite) that your plugin can interact with. Statping uses [upper.io/db.v3](https://upper.io/db.v3) for database interactions. The database is passed to your plugin `OnLoad(db sqlbuilder.Database)`, so you can use the `db` variable passed here.\n\n## Statping Plugin Interface\nPlease remember Golang plugin's are very new and Statping plugin package may change and 'could' brake your plugin. Checkout the [statup/plugin package](https://github.com/hunterlong/statping/blob/master/plugin/main.go) to see the most current interfaces.\n```go\ntype PluginActions interface {\n\tGetInfo() Info\n\tGetForm() string\n\tSetInfo(map[string]interface{}) Info\n\tRoutes() []Routing\n\tOnSave(map[string]interface{})\n\tOnFailure(map[string]interface{})\n\tOnSuccess(map[string]interface{})\n\tOnSettingsSaved(map[string]interface{})\n\tOnNewUser(map[string]interface{})\n\tOnNewService(map[string]interface{})\n\tOnUpdatedService(map[string]interface{})\n\tOnDeletedService(map[string]interface{})\n\tOnInstall(map[string]interface{})\n\tOnUninstall(map[string]interface{})\n\tOnBeforeRequest(map[string]interface{})\n\tOnAfterRequest(map[string]interface{})\n\tOnShutdown()\n\tOnLoad(sqlbuilder.Database)\n}\n```\n\n## Event Parameters\nAll event interfaces for the Statping Plugin will return a `map[string]interface{}` type, this is because the plugin package will most likely update and change in the future, but using this type will allow your plugin to continue even after updates.\n\n## Example of an Event\nKnowing what happens during an event is important for your plugin. For example, lets have an event that echo something when a service has a Failure status being issued. Checkout some example below to see how this golang plugin action works. \n\n```go\nfunc (p pkg) OnSuccess(data map[string]interface{}) {\n fmt.Println(\"Statping Example Plugin received a successful service hit! \")\n fmt.Println(\"Name: \", data[\"Name\"])\n fmt.Println(\"Domain: \", data[\"Domain\"])\n fmt.Println(\"Method: \", data[\"Method\"])\n fmt.Println(\"Latency: \", data[\"Latency\"])\n}\n```\n###### OnSuccess is fired every time a service has check it be online\n\n```go\nfunc OnFailure(service map[string]interface{}) {\n fmt.Println(\"oh no! an event is failing right now! do something!\")\n fmt.Println(service)\n}\n```\n###### OnFailure is fired every time a service is failing\n\n```go\nfunc (p pkg) OnLoad(db sqlbuilder.Database) {\n fmt.Println(\"=============================================================\")\n fmt.Printf(\" Statping Example Plugin Loaded using %v database\\n\", db.Name())\n fmt.Println(\"=============================================================\")\n}\n```\n###### OnLoad is fired after plugin is loaded into the environment\n\n\n## Interacting with Database\nThe Example Statping Plugin includes a variable `Database` that will allow you to interact with the Statping database. Checkout [database.go](https://github.com/hunterlong/statping_plugin/blob/master/database.go) to see a full example of Create, Read, Update and then Deleting a custom Communication entry into the database.\n```go\n// Insert a new communication into database\n// once inserted, return the Communication\nfunc (c *Communication) Create() *Communication {\n\tuuid, err := CommunicationTable().Insert(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tc.Id = uuid.(int64)\n\treturn c\n}\n```\n\n## Custom HTTP Routes\nPlugin's can include their own HTTP route to accept GET/POST requests. Route are loaded after Statping loads all of it's Routes. Checkout [routes.go](https://github.com/hunterlong/statping_plugin/blob/master/routes.go) on the example plugin to see a full example of how to use it.\n```go\n// You must have a Routes() method in your plugin\nfunc (p *pkg) Routes() []plugin.Routing {\n\treturn []plugin.Routing{{\n\t\tURL: \"hello\",\n\t\tMethod: \"GET\",\n\t\tHandler: CustomInfoHandler,\n\t}}\n}\n\n// This is the HTTP handler for the '/hello' URL created above\nfunc CustomInfoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintln(w, \"Oh Wow!!! This is cool...\")\n}\n```\n\n\n## Plugin To-Do List\n- [ ] Ability to includes assets like jpg, json, etc\n\n

Statuper

\nStatping includes a simple to use installation shell script that will help you install locally, Docker, and even onto a AWS EC2 instance.\n\n

\n\n

\n\n## Installation\n```bash\ncurl -O https://assets.statup.io/statuper && chmod +x statuper\n```\n\n## Usage\n- `statuper`\n\n

Build and Test

\nBuilding from the Go Language source code is pretty easy if you already have Go installed. Clone this repo and `cd` into it. \n\n### Git n' Go Get\n```bash\ngit clone https://github.com/hunterlong/statping.git\ncd statup\ngo get -v\n```\n\n### Install go.rice\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\ngo get github.com/GeertJohan/go.rice\ngo get github.com/GeertJohan/go.rice/rice\n```\n\n### Build Statping Binary\nStatping uses go.rice to compile HTML, JS, and CSS files into it's single binary output.\n```\nrice embed-go\ngo build -o statup .\n./statup version\n```\n\n### Test Coverage\nYou can also test Statio on your localhost, but it does require a MySQL, and Postgres server to be accessible since testing does create/drop tables for multiple databases. \n```\ngo test -v\n```\n\n

Contributing

\nHave a feature you want to implement into Statping!? Awesome! Follow this guide to see how you can test, compile and build Statping for production use. I recommend you use `make` with this process, it will save you time and it will auto include many customized parameters to get everything working correctly.\n\n# Dependencies\nStatping has a couple of required dependencies when testing and compiling the binary. The [Makefile](https://github.com/hunterlong/statping/blob/master/Makefile) will make these tasks a lot easier. Take a look at the Makefile to see what commands are ran. Run the command below to get setup right away.\n```bash\nmake dev-deps\n```\nList of requirements for compiling assets, building binary, and testing.\n- [Go Language](https://golang.org/) (currently `1.10.3`)\n- [Docker](https://docs.docker.com/)\n- [SASS](https://sass-lang.com/install)\n- [Cypress](https://www.cypress.io/) (only used for UI testing, `make cypress-install`)\n\n# Compiling Assets\nThis Golang project uses [rice](https://github.com/GeertJohan/go.rice) to compile static assets into a single file. The file `source/rice-box.go` is never committed to the Github repo, it is automatically created on build. Statping also requires `sass` to be installed on your local OS. To compile all the static assets run the command below:\n\n```bash\nmake compile\n```\nAfter this is complete, you'll notice the `source/rice-box.go` file has been generated. You can now continue to build, and test.\n\n# Testing\nStatping includes multiple ways to Test the application, you can run the `make` command, or the normal `go test` command. To see the full experience of your updates, you can even run Cypress tests which is in the `.dev/test` folder.\n\nStatping will run all tests in `cmd` folder on MySQL, Postgres, and SQLite databases. You can run `make databases` to automatically create MySQL and Postgres with Docker.\n\n###### Go Unit Testing:\n```bash\nmake test\n```\n\n###### Cypress UI Testing:\n```bash\nmake cypress-test\n```\n\n###### Test Everything:\n```bash\nmake test-all\n```\n\n# Build\nStatping will build on all operating systems except Windows 32-bit. I personally use [xgo](https://github.com/karalabe/xgo) to cross-compile on multiple systems using Docker. Follow the commands below to build on your local system.\n\n###### Build for local operating system:\n```bash\nmake build\n```\n\n# Compile for Production\nOnce you've tested and built locally, you can compile Statping for all available operating systems using the command below. This command will require you to have Docker.\n\n```bash\nmake build-all\n```\n\n# What Now\nEverything tested, compiled and worked out!? Awesome! 💃 You can now commit your changes, and submit a Pull Request with the features/bugs you added or removed.\n\n\n\n\n\n

PGP Signature

\nYou can check if the Statping binary you downloaded is authentic by running a few commands.\n\n### Steps to Authenticate\n1. Download the Statping `tar.gz` file from [Latest Releases](https://github.com/hunterlong/statping/releases/latest) and extract the `statping` binary and the `statup.asc` file.\n2. Run command: `gpg --verify statping.asc`\n3. You should see `Good signature from \"Hunter Long \" [ultimate]`.\n\n# Statping Public Key\n- [https://statping.com/statping.gpg](https://statping.com/statping.gpg)\n\nYou can also download the key with the command below:\n```\nwget https://statping.com/statping.gpg\n```\n\n###### `statping.gpg`\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFwGUYIBEADNsDY4aUOx8EoZuTRFPtjuadJzFRyKtHhw/tLlAnoFACanZPIT\nNZoRYvRR5v6lMDXdxsteDbJEOhZ1WDiKIr4OyMahPsyyH6ULzSBKgePUswa0sDef\nUnXYzPFQCzqQyQQFbp9AYfDP7dW6dTL9I6qU2NqlJvjxJiiZTAq87SmsLqHiASnI\n+ottnQuu6vJQBJz2PFIuaS1c3js/+HBbth9GK5B9YN1BIIyZoFmWKVU9HnJf+aM3\nUs6OLjjwYwWzQH38ZV84IjVXyiP9PQVhlCXeHK7XdhPZvnSP1m5Wszj/jowwY6Mz\nLgLotfL540X7yOJ7hJTFYLFBOtJdJr/3Ov8SH4HXdPFPVG+UqxsmtmPqUQ9iAxAE\njRFfkAxBvH5Szf2WZdaLnlrrOcOKJIIjZgHqalquBTAhlh5ul0lUVSSPxetwIBlW\n60L41k94NJFGDt8xOJ+122mLcywmQ1CzhDfeIKlxl6JDiVHjoRqlQQrqIoNZMV85\nrzGfrmbuwv1MXGBJoiNy3330ujOBmhQ9dQVwKpxhBKdjnAgIGM9szbUYxIkGgM1O\nU4b1WF3AF/9JOpKJ0LewslpM3BFFYnemGsHXAv3TBPqKidNdwMAiBOtNykGoXF6i\n0D6jOW/IB1da0gUA+kr5JdAOwIG7JXKhur2MO7Ncid59DL2N8RePRWj+jwARAQAB\ntB9IdW50ZXIgTG9uZyA8aW5mb0BzdGF0cGluZy5jb20+iQJOBBMBCAA4FiEEt21h\n+qbbdZRm6D2ZZLnGquLVUngFAlwGUYICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQZLnGquLVUnizwA//c7vmwTMq/8LYlbo37WM2kDE9AKIrz6VSMq4RhGbC\nLikH0X0epa+if79n9BZrVU/Af3aKTn7vu2J4XrvzcdCXtcsR0YmCWML2Y6OSFmhX\nw3o6woiFcp+SUWdcM/kithRun+j9sKV4akdgkdBQUdh/RMVln+radz1c6G59iTdh\nS+Ip3ObO7Gn5VnrLwxix+W9Jhg8YhDgDGEDt8e1yvjuMRY+WhjHFlwEMoE0kvQL8\nQvQH2dGD3dExWAuIL7+0xC0ZGU0PR8vRrq1ukdIsWlDY+42vvhcyPZKFFDTM/QLF\nFcCNiPSGhiK/NQq67xnRMFdh0fnqbydWj2atMpacIrheEkOt8db2/UMyDOwlIxgy\nKOG8x+yNKiG9LyvW4axRLctN608/+TbvtFo5TVOFJYxJQp4b5uz7LgJAJw7PBvfC\nbqx64BH8WGzgyGcAl9unQEtpDuxXoKvP2kbsS7hjvhK0gJgW9llpV4sRJJGApTBc\nWtbcS9DBGs3k1aZdA72bxnayD32syVz7czl4+tkRsbQZ4VgJh1yrHIDsdWQXFnYu\nEQJfCgX5HvvC13MpDUth0NWCFtWQirY3EFbIgSuhB/D5iXA+Dt1Dq5c1u7wQlUVi\nLQCU++oMGrlU3gZrnov5lnBGCEjn0O9bKQm8zmLdEcENFxUZvfPjOIY64YprZxD9\nBv65Ag0EXAZRggEQAMmjHmnvH8SvNJhku/oI96dFKen3bg9xdaFUD1vAuNglCalH\nwgXcCZd0RdobYNG46cXTzTQadtHS4hi/UBJ+oy5ZUpIRglW12eTYtqM2G11VbcQi\nj6rLITP9NIP+G1xBICSYK4UwmH55BolMEQ/1ZX0a9rESM9stDNglheCCudbMGR/1\nZYnufdEsh0yPwyC/1upZeu8LPWK62pt9mE/gccx77QTeDi5OJcRf1fPbUTCm3vSS\nwPPV2AGANodIhostjDymt5vh0tGwc7oUZZLnVdErfuctv7yMgZdiCpYu0jFy1NYf\nJgOpZasrcK7/1ozGzsfAo/sSU4kIkMwuWGgqfx5kGRK2CgU4T0i7oI6DMpOX9ZS8\ns3+oCWu83X0ofvm5R2CbjiUj2gR6JOhBQbJpCeTkLe+SFcUpnyrr7lG8B8QZHm5N\nnBi05V/s63RE3g/6WpR/fWuh+uswe01uqlSx9deW7jT49BL/MdSxwjfwLBLz/hLM\n0ld385XAd9bqMjUtp0XhZX2YORx3f/aKY7PYA62baGibb5RdPRw6viEAWU20eb+8\nX9Pa7hGmwUeal5lka4SD/TGl7wdY+g4oYP+jtKinH/ZftWA5wHTe3jWT5bdWrT2d\ne+0qA0SBkmKIDLpktvtTa19w2nfwBIwJ6fN36ZjYpOn/stxR7aRtnhSqvzxbABEB\nAAGJAjYEGAEIACAWIQS3bWH6ptt1lGboPZlkucaq4tVSeAUCXAZRggIbDAAKCRBk\nucaq4tVSeGWmD/9Pg1x6s98zdZCQa2apmUnuoQAQA9Gf2RBBuglCDGsY67wbvdHZ\n9wdFRs2QEhl2O3oFmidxthBOBRl9z62nXliLwNn1Lcy/yDfaB8wH6gMm4jn2N/z9\nvQXnyIzg8m4PItZ1p5mnY3qH5lpGF8r9Gb7tzK10rqulM2XTDojZOevlEGI6LGw8\nFjccXtNquqGZwxzytmKF3T7UBmpmt2qock8N5iJn987m6WeYmbFNc0ii0guHfdtO\nzQcItz2ngCdyvfgQPwCAoAv72ysSGhz5KZgAXRrEdcqj6Jw3ivoEUKq1aUrYncXQ\n3zC3ED6AjWOGRzjvTZzj22IVacUZ0gqx0x/oldXLOhMB9u6nFXHKj1n9nc0XHMNi\nLp9EuvQgcNLjFZGE9sxh25u9V+OhItfT/aarYEu/Xq0IkUUcdz4GehXth1/Cq1wH\nlSUie4nCs7I7OWhqMNClqP7ywElDXsQ66MCgvf01Dh64YUVjJNnyyK0QiYlCx/JQ\nZ85hNLtVXZfYqC5BRZlVFp8I8Rs2Qos9YEgn2M22+Rj+RIeD74LZFB7Q4myRvTMB\n/P466dFI83KYhwvjBYOP3jPTrV7Ky8poEGifQp2mM294CFIPS7z0z7a8+yMzcsRP\nOluFxewsEO0QNDrfFb+0gnjYlnGqOFcZjUMXbDdY5oLSPtXohynuTK1qyQ==\n=Xn0G\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n

Testing

\nIf you want to test your updates with the current golang testing units, you can follow the guide below to run a full test process. Each test for Statping will run in MySQL, Postgres, and SQlite to make sure all database types work correctly.\n\n## Create Docker Databases\nThe easiest way to run the tests on all 3 databases is by starting temporary databases servers with Docker. Docker is available for Linux, Mac and Windows. You can download/install it by going to the [Docker Installation](https://docs.docker.com/install/) site.\n\n```go\ndocker run -it -d \\\n -p 3306:3306 \\\n -env MYSQL_ROOT_PASSWORD=password123 \\\n -env MYSQL_DATABASE=root mysql\n```\n\n```go\ndocker run -it -d \\\n -p 5432:5432 \\\n -env POSTGRES_PASSWORD=password123 \\\n -env POSTGRES_USER=root \\\n -env POSTGRES_DB=root postgres\n```\n\nOnce you have MySQL and Postgres running, you can begin the testing. SQLite database will automatically create a `statup.db` file and will delete after testing.\n\n## Run Tests\nInsert the database environment variables to auto connect the the databases and run the normal test command: `go test -v`. You'll see a verbose output of each test. If all tests pass, make a push request! 💃\n```go\nDB_DATABASE=root \\\n DB_USER=root \\\n DB_PASS=password123 \\\n DB_HOST=localhost \\\n go test -v\n```\n\n

Deployment

\nStatping is a pretty cool server for monitoring your services. The way we deploy might be a little cooler though. Statping is using the most bleeding edge technology to release updates and distribute binary files automatically.\n\n1. Source code commits get pushed to Github\n2. [Rice](https://github.com/GeertJohan/go.rice) will compile all the static assets into 1 file (rice-box.go in source)\n3. SASS will generate a compiled version of the CSS. \n4. Statping Help page is generated by cloning the Wiki repo using `go generate`.\n5. Travis-CI tests the Golang application.\n6. Travis-CI tests the Statping API using [Postman](https://github.com/hunterlong/statping/blob/master/source/tmpl/postman.json).\n7. If all tests are successful, Travis-CI will compile the binaries using [xgo](https://github.com/karalabe/xgo).\n8. Binaries are code signed using the official [PGP key](https://github.com/hunterlong/statping/wiki/PGP-Signature) and compressed.\n9. [Docker](https://cloud.docker.com/repository/docker/hunterlong/statping/builds) receives a trigger to build for the `latest` tag.\n10. Travis-CI uploads the [latest release](https://github.com/hunterlong/statping/releases) as a tagged version on Github.\n11. Travis-CI updates the [homebrew-statping](https://github.com/hunterlong/homebrew-statping) repo with the latest version.\n\nAnd that's it! Statping is ready to be shipped and installed.\n\n") From 429788ef87d010ced19e69cf2262d59b55f504cf Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 22 Nov 2019 13:17:37 -0800 Subject: [PATCH 054/100] fixes, go mod --- core/notifier/example_test.go | 4 +- core/notifier/notifiers_test.go | 10 +- go.sum | 246 ++++++++++++++++++++++++++++++++ source/wiki.go | 2 +- 4 files changed, 250 insertions(+), 12 deletions(-) create mode 100644 go.sum diff --git a/core/notifier/example_test.go b/core/notifier/example_test.go index 9fbb231a..7fed86b0 100644 --- a/core/notifier/example_test.go +++ b/core/notifier/example_test.go @@ -233,7 +233,7 @@ func ExampleNotification_OnSuccess() { example.AddQueue("example", msg) fmt.Println(len(example.Queue)) // Output: - // Notifier 'Example' added new item (example) to the queue. (1 queued) + // INFO: Notifier 'Example' added new item (example) to the queue. (1 queued) // 1 } @@ -270,7 +270,7 @@ func ExampleNotification_AddQueue() { queue := example.Queue fmt.Printf("Example has %v items in the queue", len(queue)) // Output: - // Notifier 'Example' added new item (example) to the queue. (2 queued) + // INFO: Notifier 'Example' added new item (example) to the queue. (2 queued) // Example has 2 items in the queue } diff --git a/core/notifier/notifiers_test.go b/core/notifier/notifiers_test.go index d6a2d8e9..fe238bfb 100644 --- a/core/notifier/notifiers_test.go +++ b/core/notifier/notifiers_test.go @@ -72,7 +72,7 @@ func TestIsBasicType(t *testing.T) { } func TestIsInDatabase(t *testing.T) { - in := isInDatabase(example.Notification) + in := isInDatabase(example) assert.True(t, in) } @@ -88,14 +88,6 @@ func TestAddQueue(t *testing.T) { msg := "this is a test in the queue!" example.AddQueue(fmt.Sprintf("service_%v", 0), msg) assert.Equal(t, 1, len(example.Queue)) - example.AddQueue(fmt.Sprintf("service_%v", 0), msg) - assert.Equal(t, 2, len(example.Queue)) - example.AddQueue(fmt.Sprintf("service_%v", 0), msg) - assert.Equal(t, 3, len(example.Queue)) - example.AddQueue(fmt.Sprintf("service_%v", 0), msg) - assert.Equal(t, 4, len(example.Queue)) - example.AddQueue(fmt.Sprintf("service_%v", 0), msg) - assert.Equal(t, 5, len(example.Queue)) } func TestNotification_Update(t *testing.T) { diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..79d4cb6f --- /dev/null +++ b/go.sum @@ -0,0 +1,246 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +github.com/99designs/gqlgen v0.10.1 h1:1BgB6XKGTHq7uH4G1/PYyKe2Kz7/vw3AlvMZlD3TEEY= +github.com/99designs/gqlgen v0.10.1/go.mod h1:IviubpnyI4gbBcj8IcxSSc/Q/+af5riwCmJmwF0uaPE= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.0.2 h1:xKF7WlEzoa+ZVkzBxy0ukdzI2etYiWGlTPMNTBGncKI= +github.com/agnivade/levenshtein v1.0.2/go.mod h1:JLvzGblJATanj48SD0YhHTEFGkWvw3ASLFWSiMIFXsE= +github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d h1:ZX0t+GA3MWiP7LWt5xWOphWRQd5JwL4VW5uLW83KM8g= +github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d/go.mod h1:EcJ034SpbWy4heOSDiBZJRn3b5wKJM1b4sFfYeVAkI4= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/daaku/go.zipexe v1.0.1 h1:wV4zMsDOI2SZ2m7Tdz1Ps96Zrx+TzaK15VbUaGozw0M= +github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= +github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= +github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8= +github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f h1:onGP+qmYmjKs7pkmi9j0mwyr97/D5wki80e74aKIOxg= +github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f/go.mod h1:cq57a4l475CeMvE7RRpSui1MEqCmhirIt1E7kl8BC2Q= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e h1:nt2877sKfojlHCTOBXbpWjBkuWKritFaGIfgQwbQUls= +github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e/go.mod h1:B4+Kq1u5FlULTjFSM707Q6e/cOHFv0z/6QRoxubDIQ8= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= +github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA= +golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw= +golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= diff --git a/source/wiki.go b/source/wiki.go index af55c46b..bf56bf51 100644 --- a/source/wiki.go +++ b/source/wiki.go @@ -1,6 +1,6 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2019-11-22 11:19:08.753313 -0800 PST m=+0.567340112 +// 2019-11-22 13:12:12.290839 -0800 PST m=+0.637127810 // // This contains the most recently Markdown source for the Statping Wiki. package source From 8169b30f3f29ad92d3756e0e1a5ec236da39fd03 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 22 Nov 2019 14:17:12 -0800 Subject: [PATCH 055/100] postman --- source/tmpl/postman.json | 160 +++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/source/tmpl/postman.json b/source/tmpl/postman.json index 39e1c167..a6709687 100644 --- a/source/tmpl/postman.json +++ b/source/tmpl/postman.json @@ -2596,86 +2596,6 @@ { "name": "Checkins", "item": [ - { - "name": "View All Checkin's", - "event": [ - { - "listen": "test", - "script": { - "id": "54bded63-de27-4839-8783-25874c35c3ea", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"View All Checkins\", function () {", - " var jsonData = pm.response.json();", - " var first = jsonData[0];", - " var id = pm.globals.get(\"checkin_id\");", - " pm.expect(first.name).to.eql(\"Server Checkin\");", - " pm.expect(first.api_key).to.eql(id);", - " pm.expect(first.grace).to.eql(60);", - " pm.expect(first.interval).to.eql(900);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/api/checkins", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "checkins" - ] - }, - "description": "View an array of all the Checkin's inserted into the system." - }, - "response": [ - { - "name": "View All Checkin's", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/api/checkins", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "checkins" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Date", - "value": "Mon, 10 Dec 2018 19:34:16 GMT" - }, - { - "key": "Content-Length", - "value": "268" - } - ], - "cookie": [], - "body": "[\n {\n \"id\": 5,\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60,\n \"api_key\": \"1emn9ha\",\n \"created_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"updated_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"failing\": false,\n \"last_hit\": \"0001-01-01T00:00:00Z\",\n \"hits\": [],\n \"failures\": null\n }\n]" - } - ] - }, { "name": "Create Checkin", "event": [ @@ -2779,6 +2699,86 @@ } ] }, + { + "name": "View All Checkin's", + "event": [ + { + "listen": "test", + "script": { + "id": "54bded63-de27-4839-8783-25874c35c3ea", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"View All Checkins\", function () {", + " var jsonData = pm.response.json();", + " var first = jsonData[0];", + " var id = pm.globals.get(\"checkin_id\");", + " pm.expect(first.name).to.eql(\"Server Checkin\");", + " pm.expect(first.api_key).to.eql(id);", + " pm.expect(first.grace).to.eql(60);", + " pm.expect(first.interval).to.eql(900);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/checkins", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "checkins" + ] + }, + "description": "View an array of all the Checkin's inserted into the system." + }, + "response": [ + { + "name": "View All Checkin's", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/checkins", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "checkins" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Mon, 10 Dec 2018 19:34:16 GMT" + }, + { + "key": "Content-Length", + "value": "268" + } + ], + "cookie": [], + "body": "[\n {\n \"id\": 5,\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60,\n \"api_key\": \"1emn9ha\",\n \"created_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"updated_at\": \"2018-12-10T11:34:10.991372-08:00\",\n \"failing\": false,\n \"last_hit\": \"0001-01-01T00:00:00Z\",\n \"hits\": [],\n \"failures\": null\n }\n]" + } + ] + }, { "name": "Run Checkin", "event": [ From b75ae61de2e28a79fc6786be04e37af2e596be47 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Thu, 28 Nov 2019 22:26:49 +0100 Subject: [PATCH 056/100] Add sub-text for 'Send Updates only' Button This Patch adds an sub-text on the 'Send Updates only' button in the Settings Menu. Signed-off-by: Emanuel Bennici --- source/tmpl/settings.gohtml | 1 + 1 file changed, 1 insertion(+) diff --git a/source/tmpl/settings.gohtml b/source/tmpl/settings.gohtml index 275521f5..81eab96c 100644 --- a/source/tmpl/settings.gohtml +++ b/source/tmpl/settings.gohtml @@ -45,6 +45,7 @@ + Enabling this will send only Messages when the status of a services changes. From c1f4ee865afd2877d1855956951373bebf0e51e0 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Thu, 28 Nov 2019 23:34:12 +0100 Subject: [PATCH 057/100] Fix wrong Downtime Calculation The downtime was calculated wrong because the time when the latest failure oocured was subtraced of the time when the latest failure occured. And this will be zero. Now it subtractes from the Date of the latest failure the Date of the latest successfull hit. Signed-off-by: Emanuel Bennici --- core/services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services.go b/core/services.go index 6c78ab36..a4212680 100644 --- a/core/services.go +++ b/core/services.go @@ -260,7 +260,7 @@ func (s *Service) Downtime() time.Duration { if len(hits) == 0 { return time.Now().UTC().Sub(fail.CreatedAt.UTC()) } - since := fail.CreatedAt.UTC().Sub(fail.CreatedAt.UTC()) + since := fail.CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC()) return since } From c920456b9f57d154d51d54477d7b8e735546afda Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Fri, 29 Nov 2019 00:02:57 +0100 Subject: [PATCH 058/100] Set true as default value for option 'UpdateNoify' Since the 'Send only notifications on Service Status change' Feature is a nice thing it can be enabled by default, so it does not annoy with a huge amounth of down Messages. This is realted to Issue #301 Signed-off-by: Emanuel Bennici --- types/core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/core.go b/types/core.go index ca73c684..95611c0f 100644 --- a/types/core.go +++ b/types/core.go @@ -37,7 +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"` + UpdateNotify NullBool `gorm:"column:update_notify;default:true" 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"` From f5b56954860626c1a8668de0a90d942e1b32b592 Mon Sep 17 00:00:00 2001 From: robinknaapen Date: Wed, 4 Dec 2019 15:41:40 +0100 Subject: [PATCH 059/100] Handle zoomed event --- source/tmpl/service.gohtml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/source/tmpl/service.gohtml b/source/tmpl/service.gohtml index b7198e7f..02ba771a 100644 --- a/source/tmpl/service.gohtml +++ b/source/tmpl/service.gohtml @@ -318,6 +318,21 @@ var heat_options = { } }; +async function zoomedEvent(chart, { xaxis, yaxis }) { + let start = Math.round(xaxis.min / 1000), + end = Math.round(xaxis.max / 1000); + + let chartData = await ChartLatency({{$s.Id}}, start, end); + if (!chartData) { + chartData = await ChartLatency({{$s.Id}}, start, end, "minute"); + } + + if(!chartData || !chartData.length) { + return false + } + + chart.updateSeries([{ data: chartData }]); +} async function RenderHeatmap() { let heatChart = new ApexCharts( @@ -335,8 +350,12 @@ async function RenderHeatmap() { } async function RenderChartLatency() { - options.fill.colors = {{if $s.Online}}["#48d338"]{{else}}["#dd3545"]{{end}}; - options.stroke.colors = {{if $s.Online}}["#3aa82d"]{{else}}["#c23342"]{{end}}; + options.chart.events = { + zoomed: zoomedEvent, + } + options.fill.colors = {{if $s.Online}}["#48d338"]{{else}}["#dd3545"]{{end}}; + options.stroke.colors = {{if $s.Online}}["#3aa82d"]{{else}}["#c23342"]{{end}}; + let chart = new ApexCharts(document.querySelector("#service"), options); await RenderChart(chart,{{$s.Id}},{{.StartUnix}},{{.EndUnix}},"hour"); } From 107c8758ac545841da34407130aaf9e6dd127f39 Mon Sep 17 00:00:00 2001 From: Emanuel Bennici Date: Wed, 11 Dec 2019 18:40:05 +0100 Subject: [PATCH 060/100] Change 'messages' to 'notifications' --- source/tmpl/settings.gohtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tmpl/settings.gohtml b/source/tmpl/settings.gohtml index 81eab96c..11954288 100644 --- a/source/tmpl/settings.gohtml +++ b/source/tmpl/settings.gohtml @@ -45,7 +45,7 @@ - Enabling this will send only Messages when the status of a services changes. + Enabling this will send only notifications when the status of a services changes. From 31fc0e57abeb699781c1c5a76347868d277b62e6 Mon Sep 17 00:00:00 2001 From: InnovativeInventor Date: Sun, 15 Dec 2019 10:22:02 -0500 Subject: [PATCH 061/100] Fixed potential timing attack --- handlers/handlers.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index 18cc1bef..51b6d13b 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -16,19 +16,21 @@ package handlers import ( + "crypto/subtle" "crypto/tls" "encoding/json" "fmt" - "github.com/gorilla/sessions" - "github.com/hunterlong/statping/core" - "github.com/hunterlong/statping/source" - "github.com/hunterlong/statping/types" - "github.com/hunterlong/statping/utils" "html/template" "net/http" "os" "strings" "time" + + "github.com/gorilla/sessions" + "github.com/hunterlong/statping/core" + "github.com/hunterlong/statping/source" + "github.com/hunterlong/statping/types" + "github.com/hunterlong/statping/utils" ) const ( @@ -105,14 +107,14 @@ func IsReadAuthenticated(r *http.Request) bool { var token string query := r.URL.Query() key := query.Get("api") - if key == core.CoreApp.ApiKey { + if subtle.ConstantTimeCompare([]byte(key), []byte(core.CoreApp.ApiKey)) == 1 { return true } tokens, ok := r.Header["Authorization"] if ok && len(tokens) >= 1 { token = tokens[0] token = strings.TrimPrefix(token, "Bearer ") - if token == core.CoreApp.ApiKey { + if subtle.ConstantTimeCompare([]byte(token), []byte(core.CoreApp.ApiKey)) == 1 { return true } } @@ -136,7 +138,7 @@ func IsFullAuthenticated(r *http.Request) bool { if ok && len(tokens) >= 1 { token = tokens[0] token = strings.TrimPrefix(token, "Bearer ") - if token == core.CoreApp.ApiSecret { + if subtle.ConstantTimeCompare([]byte(token), []byte(core.CoreApp.ApiKey)) == 1 { return true } } From 05ff5c5e2ec4e689d95fb014f1ae12978fde0cb0 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Wed, 18 Dec 2019 03:36:29 -0800 Subject: [PATCH 062/100] temp --- Makefile | 12 ++++++++++- core/notifier/notifiers.go | 16 ++++++++++++++ handlers/routes.go | 1 + handlers/services.go | 4 ++++ notifiers/webhook.go | 16 +------------- source/tmpl/settings.gohtml | 43 +++++++++++++++++++++++-------------- 6 files changed, 60 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 8f560f62..5a7a26b1 100644 --- a/Makefile +++ b/Makefile @@ -71,10 +71,20 @@ install: build run: build ./$(BINARY_NAME) --ip 0.0.0.0 --port 8080 +# run Statping with Delve for debugging +rundlv: + dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./$(BINARY_NAME) + +builddlv: + $(GOBUILD) -gcflags "all=-N -l" -o ./$(BINARY_NAME) -v ./cmd + +watch: + reflex -v -r '\.go' -s -- sh -c 'make builddlv && make rundlv' + # compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go compile: generate sass source/scss/base.scss source/css/base.css - cd source && $(GOPATH)/bin/rice embed-go + cd source && rice embed-go rm -rf .sass-cache # benchmark testing diff --git a/core/notifier/notifiers.go b/core/notifier/notifiers.go index 1de0e1b6..0e3d86ac 100644 --- a/core/notifier/notifiers.go +++ b/core/notifier/notifiers.go @@ -461,3 +461,19 @@ func (n *Notification) IsRunning() bool { return true } } + +// ExampleService can be used for the OnTest() method for notifiers +var ExampleService = &types.Service{ + Id: 1, + Name: "Interpol - All The Rage Back Home", + Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU", + ExpectedStatus: 200, + Interval: 30, + Type: "http", + Method: "GET", + Timeout: 20, + LastStatusCode: 404, + Expected: types.NewNullString("test example"), + LastResponse: "this is an example response", + CreatedAt: time.Now().Add(-24 * time.Hour), +} diff --git a/handlers/routes.go b/handlers/routes.go index bd1ca48e..2c3bce0d 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -92,6 +92,7 @@ func Router() *mux.Router { r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET") r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET") r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET") + r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET") r.Handle("/group/{id}", http.HandlerFunc(groupViewHandler)).Methods("GET") diff --git a/handlers/services.go b/handlers/services.go index 8873926b..453b9050 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -335,3 +335,7 @@ func apiServiceHitsHandler(w http.ResponseWriter, r *http.Request) { returnJson(hits, w, r) } + +func createServiceHandler(w http.ResponseWriter, r *http.Request) { + ExecuteResponse(w, r, "service_create.gohtml", core.CoreApp, nil) +} diff --git a/notifiers/webhook.go b/notifiers/webhook.go index c76b69a9..41b1e03a 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -129,21 +129,7 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { } func (w *webhooker) OnTest() error { - service := &types.Service{ - Id: 1, - Name: "Interpol - All The Rage Back Home", - Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU", - ExpectedStatus: 200, - Interval: 30, - Type: "http", - Method: "GET", - Timeout: 20, - LastStatusCode: 404, - Expected: types.NewNullString("test example"), - LastResponse: "this is an example response", - CreatedAt: time.Now().Add(-24 * time.Hour), - } - body := replaceBodyText(w.Var2, service, nil) + body := replaceBodyText(w.Var2, notifier.ExampleService, nil) resp, err := w.sendHttpWebhook(body) if err != nil { return err diff --git a/source/tmpl/settings.gohtml b/source/tmpl/settings.gohtml index 11954288..664789a3 100644 --- a/source/tmpl/settings.gohtml +++ b/source/tmpl/settings.gohtml @@ -7,10 +7,12 @@
-
-
- - - - - - - Enabling this will send only notifications when the status of a services changes. - - - -
-
-
@@ -172,6 +159,30 @@
+
+

Notifications

+ + + +
+
+ + + + + + + Enabling this will send only notifications when the status of a services changes. + + + +
+
+ + + +
+
{{if not .UsingAssets }} From d9e03871f3e859e61ab1787a653b2bc37b6c0942 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Thu, 19 Dec 2019 08:48:59 -0800 Subject: [PATCH 063/100] signal graceful method, create service url --- Makefile | 14 ++++++++++--- cmd/main.go | 12 +++++++++++ core/sample.go | 4 ++++ handlers/routes.go | 2 +- source/scss/mobile.scss | 2 +- source/tmpl/form_service.gohtml | 31 ++++++++++++++++----------- source/tmpl/messages.gohtml | 6 +++--- source/tmpl/service_create.gohtml | 9 ++++++++ source/tmpl/services.gohtml | 16 +++++++------- source/tmpl/settings.gohtml | 35 ++++++++++++++----------------- source/tmpl/users.gohtml | 4 ++-- source/wiki.go | 4 ++-- 12 files changed, 87 insertions(+), 52 deletions(-) create mode 100644 source/tmpl/service_create.gohtml diff --git a/Makefile b/Makefile index 5a7a26b1..5dc8564c 100644 --- a/Makefile +++ b/Makefile @@ -73,13 +73,21 @@ run: build # run Statping with Delve for debugging rundlv: - dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./$(BINARY_NAME) + lsof -ti:8080 | xargs kill + DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none GO_ENV=test \ + dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./statping + +killdlv: + lsof -ti:2345 | xargs kill builddlv: $(GOBUILD) -gcflags "all=-N -l" -o ./$(BINARY_NAME) -v ./cmd watch: - reflex -v -r '\.go' -s -- sh -c 'make builddlv && make rundlv' + find . -print | grep -i '.*\.\(go\|gohtml\)' | justrun -v -c \ + 'go build -v -gcflags "all=-N -l" -o statping ./cmd && make rundlv &' \ + -delay 10s -stdin \ + -i="Makefile,statping,statup.db,statup.db-journal,handlers/graphql/generated.go" # compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go compile: generate @@ -250,7 +258,7 @@ clean: rm -rf dev/test/cypress/videos rm -f coverage.* sass rm -f source/rice-box.go - rm -f *.db-journal + rm -rf **/*.db-journal rm -rf *.snap find . -name "*.out" -type f -delete find . -name "*.cpu" -type f -delete diff --git a/cmd/main.go b/cmd/main.go index b0990e07..1463ed4f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,8 @@ import ( "github.com/hunterlong/statping/utils" "github.com/joho/godotenv" "os" + "os/signal" + "syscall" ) var ( @@ -61,6 +63,7 @@ func parseFlags() { // main will run the Statping application func main() { var err error + go sigterm() parseFlags() loadDotEnvs() source.Assets() @@ -92,6 +95,15 @@ func main() { mainProcess() } +// sigterm will attempt to close the database connections gracefully +func sigterm() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL) + <-sigs + core.CloseDB() + os.Exit(1) +} + // loadDotEnvs attempts to load database configs from a '.env' file in root directory func loadDotEnvs() error { err := godotenv.Load() diff --git a/core/sample.go b/core/sample.go index 20d3c68f..f53fb386 100644 --- a/core/sample.go +++ b/core/sample.go @@ -44,6 +44,7 @@ func InsertSampleData() error { Order: 1, GroupId: 1, Permalink: types.NewNullString("google"), + VerifySSL: types.NewNullBool(true), CreatedAt: createdOn, }) s2 := ReturnService(&types.Service{ @@ -56,6 +57,7 @@ func InsertSampleData() error { Timeout: 20, Order: 2, Permalink: types.NewNullString("statping_github"), + VerifySSL: types.NewNullBool(true), CreatedAt: createdOn, }) s3 := ReturnService(&types.Service{ @@ -68,6 +70,7 @@ func InsertSampleData() error { Timeout: 30, Order: 3, Public: types.NewNullBool(true), + VerifySSL: types.NewNullBool(true), GroupId: 2, CreatedAt: createdOn, }) @@ -83,6 +86,7 @@ func InsertSampleData() error { Timeout: 30, Order: 4, Public: types.NewNullBool(true), + VerifySSL: types.NewNullBool(true), GroupId: 2, CreatedAt: createdOn, }) diff --git a/handlers/routes.go b/handlers/routes.go index 2c3bce0d..44af5554 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -89,10 +89,10 @@ func Router() *mux.Router { // SERVICE Routes r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET") + r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET") r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET") r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET") r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET") - r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET") r.Handle("/group/{id}", http.HandlerFunc(groupViewHandler)).Methods("GET") diff --git a/source/scss/mobile.scss b/source/scss/mobile.scss index 0ae37874..f5d4cdca 100644 --- a/source/scss/mobile.scss +++ b/source/scss/mobile.scss @@ -48,7 +48,7 @@ .card-body { font-size: 10pt; - padding: 0px 10px; + padding: 10px 10px; } .lg_number { diff --git a/source/tmpl/form_service.gohtml b/source/tmpl/form_service.gohtml index f9ee9376..964b84e2 100644 --- a/source/tmpl/form_service.gohtml +++ b/source/tmpl/form_service.gohtml @@ -7,6 +7,7 @@ {{else}}
{{end}} +

Basic Information

@@ -33,6 +34,21 @@ Statping will attempt to connect to this URL
+
+ +
+ + Attach this service to a group +
+
+ +

Request Details

+
@@ -80,6 +96,9 @@
+ +

Additional Options

+
@@ -138,18 +157,6 @@
-
- -
- - Attach this service to a group -
-
diff --git a/source/tmpl/messages.gohtml b/source/tmpl/messages.gohtml index 5ec025e4..4d19672e 100644 --- a/source/tmpl/messages.gohtml +++ b/source/tmpl/messages.gohtml @@ -4,7 +4,7 @@ {{template "nav"}} {{if .}}
-

Messages

+

Messages

@@ -34,7 +34,7 @@ {{end}} {{if Auth}}
-

Create Message

+

Create Message

{{template "form_message" NewMessage}}
{{end}} @@ -56,4 +56,4 @@ $(document).ready(function() { }); }); -{{end}} \ No newline at end of file +{{end}} diff --git a/source/tmpl/service_create.gohtml b/source/tmpl/service_create.gohtml new file mode 100644 index 00000000..86bb0712 --- /dev/null +++ b/source/tmpl/service_create.gohtml @@ -0,0 +1,9 @@ +{{define "title"}}Statping | Create Service{{end}} +{{define "content"}} +
+{{template "nav"}} +
+ {{template "form_service" NewService}} +
+
+{{end}} diff --git a/source/tmpl/services.gohtml b/source/tmpl/services.gohtml index f97252d4..99ce6b60 100644 --- a/source/tmpl/services.gohtml +++ b/source/tmpl/services.gohtml @@ -5,7 +5,9 @@
{{if ne (len .Services) 0}} -

Services

+

Services + Create +

@@ -21,7 +23,7 @@ @@ -36,14 +38,10 @@
{{.Name}} {{if .Online}}ONLINE{{else}}OFFLINE{{end}} - + {{if .Public.Bool}}PUBLIC{{else}}PRIVATE{{end}} {{if ne .GroupId 0}}{{(Group .GroupId).Name}}{{end}}
{{end}} - {{if Auth}} -

Create Service

- {{template "form_service" NewService}} - {{end}}
-
-

Groups

+
+

Groups

@@ -70,7 +68,7 @@
{{if Auth}} -

Create Group

+

Create Group

{{template "form_group" NewGroup}} {{end}}
diff --git a/source/tmpl/settings.gohtml b/source/tmpl/settings.gohtml index 664789a3..465f9d96 100644 --- a/source/tmpl/settings.gohtml +++ b/source/tmpl/settings.gohtml @@ -6,13 +6,16 @@