mirror of https://github.com/statping/statping
Merge branch 'master' into master
commit
a2f8582088
|
@ -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{}, ¬ifier.Notification{}}
|
||||
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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}}
|
|
@ -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
|
@ -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"`
|
||||
}
|
|
@ -1 +1 @@
|
|||
0.80.61
|
||||
0.80.62
|
||||
|
|
Loading…
Reference in New Issue