Merge branch 'master' into master

pull/208/head
Alessandro Pezzè 2019-07-03 19:00:48 +02:00 committed by GitHub
commit a2f8582088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 287 additions and 4 deletions

View File

@ -36,7 +36,7 @@ var (
)
func init() {
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, &notifier.Notification{}}
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, &notifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}}
}
// DbConfig stores the config.yml file for the statup configuration
@ -87,6 +87,16 @@ func groupsDb() *gorm.DB {
return DbSession.Model(&types.Group{})
}
// incidentsDB returns the 'incidents' database column
func incidentsDB() *gorm.DB {
return DbSession.Model(&types.Incident{})
}
// incidentsUpdatesDB returns the 'incidents updates' database column
func incidentsUpdatesDB() *gorm.DB {
return DbSession.Model(&types.IncidentUpdate{})
}
// HitsBetween returns the gorm database query for a collection of service hits between a time range
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) *gorm.DB {
selector := Dbtimestamp(group, column)
@ -323,6 +333,8 @@ func (db *DbConfig) DropDatabase() error {
err = DbSession.DropTableIfExists("services")
err = DbSession.DropTableIfExists("users")
err = DbSession.DropTableIfExists("messages")
err = DbSession.DropTableIfExists("incidents")
err = DbSession.DropTableIfExists("incident_updates")
return err.Error
}

73
core/incidents.go Normal file
View File

@ -0,0 +1,73 @@
package core
import (
"github.com/hunterlong/statping/types"
"time"
)
type Incident struct {
*types.Incident
}
type IncidentUpdate struct {
*types.IncidentUpdate
}
// AllIncidents will return all incidents and updates recorded
func AllIncidents() []*Incident {
var incidents []*Incident
incidentsDB().Find(&incidents).Order("id desc")
for _, i := range incidents {
var updates []*types.IncidentUpdate
incidentsUpdatesDB().Find(&updates).Order("id desc")
i.Updates = updates
}
return incidents
}
// Incidents will return the all incidents for a service
func (s *Service) Incidents() []*Incident {
var incidentArr []*Incident
incidentsDB().Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
return incidentArr
}
// AllUpdates will return the all updates for an incident
func (i *Incident) AllUpdates() []*IncidentUpdate {
var updatesArr []*IncidentUpdate
incidentsUpdatesDB().Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
return updatesArr
}
// Delete will remove a incident
func (i *Incident) Delete() error {
err := incidentsDB().Delete(i)
return err.Error
}
// Create will create a incident and insert it into the database
func (i *Incident) Create() (int64, error) {
i.CreatedAt = time.Now()
db := incidentsDB().Create(i)
return i.Id, db.Error
}
// Update will update a incident
func (i *Incident) Update() (int64, error) {
i.UpdatedAt = time.Now()
db := incidentsDB().Update(i)
return i.Id, db.Error
}
// Delete will remove a incident update
func (i *IncidentUpdate) Delete() error {
err := incidentsUpdatesDB().Delete(i)
return err.Error
}
// Create will create a incident update and insert it into the database
func (i *IncidentUpdate) Create() (int64, error) {
i.CreatedAt = time.Now()
db := incidentsUpdatesDB().Create(i)
return i.Id, db.Error
}

View File

@ -107,11 +107,45 @@ func InsertSampleData() error {
insertMessages()
insertSampleIncidents()
utils.Log(1, "Sample data has finished importing")
return nil
}
func insertSampleIncidents() error {
incident1 := &Incident{&types.Incident{
Title: "Github Downtime",
Description: "This is an example of a incident for a service.",
ServiceId: 2,
}}
_, err := incident1.Create()
incidentUpdate1 := &IncidentUpdate{&types.IncidentUpdate{
IncidentId: incident1.Id,
Message: "Github's page for Statping seems to be sending a 501 error.",
Type: "Investigating",
}}
_, err = incidentUpdate1.Create()
incidentUpdate2 := &IncidentUpdate{&types.IncidentUpdate{
IncidentId: incident1.Id,
Message: "Problem is continuing and we are looking at the issues.",
Type: "Update",
}}
_, err = incidentUpdate2.Create()
incidentUpdate3 := &IncidentUpdate{&types.IncidentUpdate{
IncidentId: incident1.Id,
Message: "Github is now back online and everything is working.",
Type: "Resolved",
}}
_, err = incidentUpdate3.Create()
return err
}
func insertSampleGroups() error {
group1 := &Group{&types.Group{
Name: "Main Services",

View File

@ -105,6 +105,12 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
case *types.Checkin:
objName = "checkin"
objId = v.Id
case *core.Incident:
objName = "incident"
objId = v.Id
case *core.IncidentUpdate:
objName = "incident_update"
objId = v.Id
default:
objName = "missing"
}

11
handlers/incident.go Normal file
View File

@ -0,0 +1,11 @@
package handlers
import (
"github.com/hunterlong/statping/core"
"net/http"
)
func apiAllIncidentsHandler(w http.ResponseWriter, r *http.Request) {
incidents := core.AllIncidents()
returnJson(incidents, w, r)
}

View File

@ -123,6 +123,9 @@ func Router() *mux.Router {
r.Handle("/api/services/{id}/failures", authenticated(servicesDeleteFailuresHandler, false)).Methods("DELETE")
r.Handle("/api/services/{id}/hits", authenticated(apiServiceHitsHandler, false)).Methods("GET")
// API INCIDENTS Routes
r.Handle("/api/incidents", readOnly(apiAllIncidentsHandler, false)).Methods("GET")
// API USER Routes
r.Handle("/api/users", authenticated(apiAllUsersHandler, false)).Methods("GET")
r.Handle("/api/users", authenticated(apiCreateUsersHandler, false)).Methods("POST")

View File

@ -0,0 +1,88 @@
{{define "form_incident"}}
<div class="card">
<div class="card-body">
{{$message := .}}
{{if ne .Id 0}}
<form class="ajax_form" action="/api/messages/{{.Id}}" data-redirect="/messages" method="POST">
{{else}}
<form class="ajax_form" action="/api/messages" data-redirect="/messages" method="POST">
{{end}}
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label">Title</label>
<div class="col-sm-8">
<input type="text" name="title" class="form-control" value="{{.Title}}" id="title" placeholder="Message Title" required>
</div>
</div>
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label">Description</label>
<div class="col-sm-8">
<textarea rows="5" name="description" class="form-control" id="description" required>{{.Description}}</textarea>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Message Date Range</label>
<div class="col-sm-4">
<input type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="{{ParseTime .StartOn "2006-01-02T15:04:05Z"}}" required>
</div>
<div class="col-sm-4">
<input type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="{{ParseTime .EndOn "2006-01-02T15:04:05Z"}}" required>
</div>
</div>
<div class="form-group row">
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
<div class="col-sm-8">
<select class="form-control" name="service" id="service_id">
<option value="0" {{if eq (ToString .ServiceId) "0"}}selected{{end}}>Global Message</option>
{{range Services}}
{{$s := .Select}}
<option value="{{$s.Id}}" {{if eq $message.ServiceId $s.Id}}selected{{end}}>{{$s.Name}}</option>
{{end}}
</select>
</div>
</div>
<div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
<div class="col-sm-8">
<input type="text" name="notify_method" class="form-control" id="notify_method" value="{{.NotifyMethod}}" placeholder="email">
</div>
</div>
<div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
<div class="col-sm-8">
<span class="switch">
<input type="checkbox" name="notify_users-value" class="switch" id="switch-normal"{{if .NotifyUsers.Bool}} checked{{end}}>
<label for="switch-normal">Notify Users Before Scheduled Time</label>
<input type="hidden" name="notify_users" id="switch-normal-value" value="{{if .NotifyUsers.Bool}}true{{else}}false{{end}}">
</span>
</div>
</div>
<div class="form-group row">
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
<div class="col-sm-8">
<div class="form-inline">
<input type="number" name="notify_before" class="col-4 form-control" id="notify_before" value="{{.NotifyBefore.Int64}}">
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
<option value="minute"{{if ne .Id 0}} selected{{else}}{{if eq .NotifyBeforeScale "minute"}}selected{{end}}{{end}}>Minutes</option>
<option value="hour"{{if eq .NotifyBeforeScale "hour"}} selected{{end}}>Hours</option>
<option value="day"{{if eq .NotifyBeforeScale "day"}} selected{{end}}>Days</option>
</select>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update Message{{else}}Create Message{{end}}</button>
</div>
</div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
</form>
</div>
</div>
{{end}}

View File

@ -3,6 +3,7 @@
{{ define "content" }}
{{$s := .Service}}
{{$failures := $s.LimitedFailures 16}}
{{$incidents := $s.Incidents}}
{{$checkinFailures := $s.LimitedCheckinFailures 16}}
{{$isAdmin := Auth}}
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
@ -83,6 +84,7 @@
<nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs" role="serviceLists">
{{if $isAdmin}}<a class="flex-sm-fill text-sm-center nav-link active" id="edit-tab" data-toggle="tab" href="#edit" role="tab" aria-controls="edit" aria-selected="false">Edit Service</a>{{end}}
<a class="flex-sm-fill text-sm-center nav-link{{ if not $failures }} disabled{{end}}" id="failures-tab" data-toggle="tab" href="#failures" role="tab" aria-controls="failures" aria-selected="true">Failures</a>
<a class="flex-sm-fill text-sm-center nav-link{{ if not $incidents }} disabled{{end}}" id="incidents-tab" data-toggle="tab" href="#incidents" role="tab" aria-controls="incidents" aria-selected="true">Incidents</a>
{{if $isAdmin}}<a class="flex-sm-fill text-sm-center nav-link" id="checkins-tab" data-toggle="tab" href="#checkins" role="tab" aria-controls="checkins" aria-selected="false">Checkins</a>{{end}}
<a class="flex-sm-fill text-sm-center nav-link{{if not $isAdmin}} active{{end}}" id="response-tab" data-toggle="tab" href="#response" role="tab" aria-controls="response" aria-selected="false">Response</a>
</nav>
@ -104,6 +106,36 @@
{{ end }}
</div>
{{end}}
<div class="tab-pane fade" id="incidents" role="serviceLists" aria-labelledby="incidents-tab">
{{ if $incidents }}
<div class="list-group mt-3 mb-4">
{{ range $incidents }}
<div class="list-group-item flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{.Title}}</h5>
<small>{{.CreatedAt}}</small>
</div>
<p class="mb-1">{{.Description}}</p>
<ul class="list-group mt-3">
{{ range .AllUpdates }}
<li class="list-group-item">
<p>
<span class="badge badge-primary">{{.Type}}</span>
<span class="float-right">
{{.Message}}
<p class="text-muted text-right small">{{.CreatedAt}}</p>
</span>
</p>
</li>
{{end}}
</ul>
</div>
{{ end }}
</div>
{{ end }}
</div>
{{if $isAdmin}}
<div class="tab-pane fade" id="checkins" role="serviceLists" aria-labelledby="checkins-tab">
{{if $s.AllCheckins}}

File diff suppressed because one or more lines are too long

24
types/incident.go Normal file
View File

@ -0,0 +1,24 @@
package types
import "time"
// Incident is the main struct for Incidents
type Incident struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Title string `gorm:"column:title" json:"title,omitempty"`
Description string `gorm:"column:description" json:"description,omitempty"`
ServiceId int64 `gorm:"index;column:service" json:"service"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" json:"updated_at"`
Updates []*IncidentUpdate `gorm:"-" json:"updates,omitempty"`
}
// IncidentUpdate contains updates based on a Incident
type IncidentUpdate struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
IncidentId int64 `gorm:"index;column:incident" json:"-"`
Message string `gorm:"column:message" json:"message,omitempty"`
Type string `gorm:"column:type" json:"type,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" json:"updated_at"`
}

View File

@ -1 +1 @@
0.80.61
0.80.62