mirror of https://github.com/statping/statping
upgrades
parent
b4fe30c2cc
commit
00854c930a
8
api.go
8
api.go
|
@ -10,6 +10,14 @@ func ApiIndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(core)
|
json.NewEncoder(w).Encode(core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
checkin := FindCheckin(vars["api"])
|
||||||
|
checkin.Receivehit()
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(checkin)
|
||||||
|
}
|
||||||
|
|
||||||
func ApiServiceHandler(w http.ResponseWriter, r *http.Request) {
|
func ApiServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
service, _ := SelectService(StringInt(vars["id"]))
|
service, _ := SelectService(StringInt(vars["id"]))
|
||||||
|
|
|
@ -12,6 +12,7 @@ func CheckServices() {
|
||||||
services, _ = SelectAllServices()
|
services, _ = SelectAllServices()
|
||||||
for _, v := range services {
|
for _, v := range services {
|
||||||
obj := v
|
obj := v
|
||||||
|
go obj.StartCheckins()
|
||||||
go obj.CheckQueue()
|
go obj.CheckQueue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/ararog/timeago"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Checkin struct {
|
||||||
|
Id int `db:"id,omitempty"`
|
||||||
|
Service int64 `db:"service"`
|
||||||
|
Interval int64 `db:"check_interval"`
|
||||||
|
Api string `db:"api"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
Hits int64 `json:"hits"`
|
||||||
|
Last time.Time `json:"last"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SelectAllCheckins() []*Checkin {
|
||||||
|
var checkins []*Checkin
|
||||||
|
col := dbSession.Collection("checkins").Find("service", s.Id).OrderBy("-id")
|
||||||
|
col.All(&checkins)
|
||||||
|
s.Checkins = checkins
|
||||||
|
return checkins
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Checkin) Create() (int64, error) {
|
||||||
|
u.CreatedAt = time.Now()
|
||||||
|
uuid, err := dbSession.Collection("checkins").Insert(u)
|
||||||
|
if uuid == nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
fmt.Println(uuid)
|
||||||
|
return uuid.(int64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SelectCheckinApi(api string) *Checkin {
|
||||||
|
var checkin *Checkin
|
||||||
|
dbSession.Collection("checkins").Find("api", api).One(&checkin)
|
||||||
|
return checkin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkin) Receivehit() {
|
||||||
|
c.Hits++
|
||||||
|
c.Last = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
|
||||||
|
between := time.Now().Sub(c.Last).Seconds()
|
||||||
|
if between > float64(c.Interval) {
|
||||||
|
fmt.Println("rechecking every 15 seconds!")
|
||||||
|
c.CreateFailure()
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
guard <- struct{}{}
|
||||||
|
c.RecheckCheckinFailure(guard)
|
||||||
|
} else {
|
||||||
|
fmt.Println("i recovered!!")
|
||||||
|
}
|
||||||
|
<-guard
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Checkin) CreateFailure() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Checkin) Ago() string {
|
||||||
|
got, _ := timeago.TimeAgoWithTime(time.Now(), f.Last)
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindCheckin(api string) *Checkin {
|
||||||
|
for _, s := range services {
|
||||||
|
for _, c := range s.Checkins {
|
||||||
|
if c.Api == api {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkin) Run() {
|
||||||
|
if c.Interval == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("checking: ", c.Api)
|
||||||
|
between := time.Now().Sub(c.Last).Seconds()
|
||||||
|
if between > float64(c.Interval) {
|
||||||
|
guard := make(chan struct{})
|
||||||
|
c.RecheckCheckinFailure(guard)
|
||||||
|
<-guard
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
c.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) StartCheckins() {
|
||||||
|
for _, c := range s.Checkins {
|
||||||
|
checkin := c
|
||||||
|
go checkin.Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckinProcess() {
|
||||||
|
for _, s := range services {
|
||||||
|
for _, c := range s.Checkins {
|
||||||
|
checkin := c
|
||||||
|
go checkin.Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
database.go
89
database.go
|
@ -2,8 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"upper.io/db.v3/lib/sqlbuilder"
|
"upper.io/db.v3/lib/sqlbuilder"
|
||||||
"upper.io/db.v3/mysql"
|
"upper.io/db.v3/mysql"
|
||||||
"upper.io/db.v3/postgresql"
|
"upper.io/db.v3/postgresql"
|
||||||
|
@ -63,90 +61,3 @@ func DbConnection(dbType string) error {
|
||||||
OnLoad(dbSession)
|
OnLoad(dbSession)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DropDatabase() {
|
|
||||||
fmt.Println("Dropping Tables...")
|
|
||||||
down, _ := sqlBox.String("down.sql")
|
|
||||||
requests := strings.Split(down, ";")
|
|
||||||
for _, request := range requests {
|
|
||||||
_, err := dbSession.Exec(request)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadSampleData() error {
|
|
||||||
fmt.Println("Inserting Sample Data...")
|
|
||||||
s1 := &Service{
|
|
||||||
Name: "Google",
|
|
||||||
Domain: "https://google.com",
|
|
||||||
ExpectedStatus: 200,
|
|
||||||
Interval: 10,
|
|
||||||
Port: 0,
|
|
||||||
Type: "https",
|
|
||||||
Method: "GET",
|
|
||||||
}
|
|
||||||
s2 := &Service{
|
|
||||||
Name: "Statup.io",
|
|
||||||
Domain: "https://statup.io",
|
|
||||||
ExpectedStatus: 200,
|
|
||||||
Interval: 15,
|
|
||||||
Port: 0,
|
|
||||||
Type: "https",
|
|
||||||
Method: "GET",
|
|
||||||
}
|
|
||||||
s3 := &Service{
|
|
||||||
Name: "Statup.io SSL Check",
|
|
||||||
Domain: "https://statup.io",
|
|
||||||
ExpectedStatus: 200,
|
|
||||||
Interval: 15,
|
|
||||||
Port: 443,
|
|
||||||
Type: "tcp",
|
|
||||||
}
|
|
||||||
s4 := &Service{
|
|
||||||
Name: "Github Failing Check",
|
|
||||||
Domain: "https://github.com/thisisnotausernamemaybeitis",
|
|
||||||
ExpectedStatus: 200,
|
|
||||||
Interval: 15,
|
|
||||||
Port: 0,
|
|
||||||
Type: "https",
|
|
||||||
Method: "GET",
|
|
||||||
}
|
|
||||||
s1.Create()
|
|
||||||
s2.Create()
|
|
||||||
s3.Create()
|
|
||||||
s4.Create()
|
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
s1.Check()
|
|
||||||
s2.Check()
|
|
||||||
s3.Check()
|
|
||||||
s4.Check()
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateDatabase() {
|
|
||||||
fmt.Println("Creating Tables...")
|
|
||||||
sql := "postgres_up.sql"
|
|
||||||
if dbServer == "mysql" {
|
|
||||||
sql = "mysql_up.sql"
|
|
||||||
} else if dbServer == "sqlite3" {
|
|
||||||
sql = "sqlite_up.sql"
|
|
||||||
}
|
|
||||||
up, _ := sqlBox.String(sql)
|
|
||||||
requests := strings.Split(up, ";")
|
|
||||||
for _, request := range requests {
|
|
||||||
_, err := dbSession.Exec(request)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//secret := NewSHA1Hash()
|
|
||||||
//db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan()
|
|
||||||
fmt.Println("Database Created")
|
|
||||||
//SampleData()
|
|
||||||
}
|
|
||||||
|
|
2
hits.go
2
hits.go
|
@ -38,7 +38,7 @@ func (s *Service) Hits() ([]Hit, error) {
|
||||||
|
|
||||||
func (s *Service) LimitedHits() ([]Hit, error) {
|
func (s *Service) LimitedHits() ([]Hit, error) {
|
||||||
var hits []Hit
|
var hits []Hit
|
||||||
col := hitCol().Find("service", s.Id).Limit(256).OrderBy("-id")
|
col := hitCol().Find("service", s.Id).Limit(1056).OrderBy("-id")
|
||||||
err := col.All(&hits)
|
err := col.All(&hits)
|
||||||
return hits, err
|
return hits, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
HTML,BODY {
|
HTML,BODY {
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
margin: 40px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
max-width: 860px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
margin-top: -50px;
|
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +93,8 @@ HTML,BODY {
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 0px;
|
margin-top: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
|
|
|
@ -44,14 +44,18 @@
|
||||||
|
|
||||||
{{ range .Services }}
|
{{ range .Services }}
|
||||||
|
|
||||||
{{$name := .Name}}
|
{{ if .Failures }}
|
||||||
|
<div class="list-group mt-5">
|
||||||
{{ range .Failures }}
|
{{ range .Failures }}
|
||||||
<blockquote class="blockquote text-left mt-3">
|
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
||||||
<p class="mb-0">{{$name}}</p>
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<p class="mb-0">{{.ParseError}}</p>
|
<h5 class="mb-1">{{.ParseError}}</h5>
|
||||||
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
|
<small>Reported {{.Ago}}</small>
|
||||||
</blockquote>
|
</div>
|
||||||
|
<p class="mb-1">{{.Issue}}</p>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "footer"}}
|
{{ define "footer"}}
|
||||||
<div class="footer text-center">
|
<div class="footer text-center mb-4">
|
||||||
{{ if .Core.Footer }}
|
{{ if .Core.Footer }}
|
||||||
{{ safe .Core.Footer }}
|
{{ safe .Core.Footer }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -23,29 +23,18 @@
|
||||||
|
|
||||||
<div class="col-12 mb-5">
|
<div class="col-12 mb-5">
|
||||||
|
|
||||||
{{ if .Core.AllOnline }}
|
|
||||||
<div class="alert alert-success mt-2 mb-2" role="alert">
|
|
||||||
All services are online and operational!
|
|
||||||
</div>
|
|
||||||
{{ else }}
|
|
||||||
<div class="alert alert-danger mt-2 mb-2" role="alert">
|
|
||||||
There is an offline service!
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<div class="list-group online_list">
|
<div class="list-group online_list">
|
||||||
{{ range .Services }}
|
{{ range .Services }}
|
||||||
<a href="#" class="list-group-item list-group-item-action {{if .Online}}{{ end }}">
|
<a href="#" class="list-group-item list-group-item-action {{if not .Online}}bg-danger text-white{{ end }}">
|
||||||
{{ .Name }}
|
{{ .Name }}
|
||||||
{{if .Online}}
|
{{if .Online}}
|
||||||
<span class="badge online_badge float-right">ONLINE</span>
|
<span class="badge online_badge float-right">ONLINE</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<span class="badge offline_badge float-right">OFFLINE</span>
|
<span class="badge bg-white text-black-50 float-right">OFFLINE</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,17 +58,17 @@
|
||||||
|
|
||||||
<div class="row stats_area mt-5 mb-5">
|
<div class="row stats_area mt-5 mb-5">
|
||||||
|
|
||||||
<div class="col-md-4 col-sm-12">
|
<div class="col-4">
|
||||||
<span class="lg_number">{{.Online24}}%</span>
|
<span class="lg_number">{{.Online24}}%</span>
|
||||||
Online last 24 Hours
|
Online last 24 Hours
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 col-sm-6">
|
<div class="col-4">
|
||||||
<span class="lg_number">{{.AvgTime}}ms</span>
|
<span class="lg_number">{{.AvgTime}}ms</span>
|
||||||
Average Response
|
Average Response
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 col-sm-6">
|
<div class="col-4">
|
||||||
<span class="lg_number">{{.AvgUptime}}%</span>
|
<span class="lg_number">{{.AvgUptime}}%</span>
|
||||||
Total Uptime
|
Total Uptime
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,6 +87,9 @@
|
||||||
<p class="mb-1">{{.Issue}}</p>
|
<p class="mb-1">{{.Issue}}</p>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
<span class="text-right">{{ .TotalFailures }} Total Failures</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
@ -124,20 +116,10 @@ var chartdata = new Chart(ctx, {
|
||||||
label: 'Response Time (Milliseconds)',
|
label: 'Response Time (Milliseconds)',
|
||||||
data: {{js .GraphData}},
|
data: {{js .GraphData}},
|
||||||
backgroundColor: [
|
backgroundColor: [
|
||||||
'rgba(255, 99, 132, 0.2)',
|
'rgba(47, 206, 30, 0.92)'
|
||||||
'rgba(54, 162, 235, 0.2)',
|
|
||||||
'rgba(255, 206, 86, 0.2)',
|
|
||||||
'rgba(75, 192, 192, 0.2)',
|
|
||||||
'rgba(153, 102, 255, 0.2)',
|
|
||||||
'rgba(255, 159, 64, 0.2)'
|
|
||||||
],
|
],
|
||||||
borderColor: [
|
borderColor: [
|
||||||
'rgba(255,99,132,1)',
|
'rgb(47, 171, 34)'
|
||||||
'rgba(54, 162, 235, 1)',
|
|
||||||
'rgba(255, 206, 86, 1)',
|
|
||||||
'rgba(75, 192, 192, 1)',
|
|
||||||
'rgba(153, 102, 255, 1)',
|
|
||||||
'rgba(255, 159, 64, 1)'
|
|
||||||
],
|
],
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-4">
|
<div class="col-md-4 col-sm-12">
|
||||||
|
|
||||||
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
||||||
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
|
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-md-8 col-sm-12">
|
||||||
<div class="tab-content" id="v-pills-tabContent">
|
<div class="tab-content" id="v-pills-tabContent">
|
||||||
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">
|
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">
|
||||||
<h3>Settings</h3>
|
<h3>Settings</h3>
|
||||||
|
@ -102,9 +102,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{template "footer"}}
|
{{template "footer"}}
|
||||||
|
|
||||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||||
|
|
|
@ -18,16 +18,14 @@
|
||||||
{{template "nav"}}
|
{{template "nav"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="col-12">
|
|
||||||
|
|
||||||
<div class="col-12 mb-4">
|
<div class="col-12 mb-4">
|
||||||
|
|
||||||
<h3 class="mt-2">{{ .Name }}
|
<h4 class="mt-2">{{ .Name }}
|
||||||
{{if .Online }}
|
{{if .Online }}
|
||||||
<span class="badge online_badge float-right">ONLINE</span>
|
<span class="badge online_badge float-right">ONLINE</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<span class="badge offline_badge float-right">OFFLINE</span>
|
<span class="badge offline_badge float-right">OFFLINE</span>
|
||||||
{{end}}</h3>
|
{{end}}</h4>
|
||||||
|
|
||||||
<div class="row stats_area mt-5 mb-5">
|
<div class="row stats_area mt-5 mb-5">
|
||||||
|
|
||||||
|
@ -59,6 +57,42 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<h3>Service Checkins</h3>
|
||||||
|
|
||||||
|
{{ range .Checkins }}
|
||||||
|
|
||||||
|
<div class="col-12 mt-3">
|
||||||
|
|
||||||
|
<h5>Check #{{.Id}} <span class="badge online_badge float-right">Checked in {{.Ago}}</span></h5>
|
||||||
|
|
||||||
|
<input type="text" class="form-control" value="https://domainhere.com/api/checkin/{{.Api}}">
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<form action="/service/{{.Id}}/checkin" method="POST">
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="service_name" class="col-sm-4 col-form-label">Check Interval (in seconds)</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="number" name="name" class="form-control" id="checkin_interval" value="30" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<button type="submit" class="btn btn-success">Save Checkin</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{{if Auth}}
|
{{if Auth}}
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
@ -82,8 +116,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
<label for="service_url" class="col-sm-12 col-form-label">Application Endpoint (URL)</label>
|
||||||
<div class="col-8">
|
<div class="col-12">
|
||||||
<input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com">
|
<input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,15 +166,21 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
{{ range .Failures }}
|
{{ if .LimitedFailures }}
|
||||||
<blockquote class="blockquote text-right mt-3">
|
<div class="list-group mt-5">
|
||||||
<p class="mb-0">{{.ParseError}}</p>
|
{{ range .LimitedFailures }}
|
||||||
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
|
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
||||||
</blockquote>
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">{{.ParseError}}</h5>
|
||||||
|
<small>Reported {{.Ago}}</small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">{{.Issue}}</p>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "footer"}}
|
{{template "footer"}}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">#</th>
|
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Status</th>
|
<th scope="col">Status</th>
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
|
@ -32,7 +31,6 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range .}}
|
{{range .}}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{.Id}}</th>
|
|
||||||
<td>{{.Name}}</td>
|
<td>{{.Name}}</td>
|
||||||
<td>{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
|
<td>{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
@ -66,8 +64,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
<label for="service_url" class="col-sm-12 col-form-label">Application Endpoint (URL)</label>
|
||||||
<div class="col-8">
|
<div class="col-12">
|
||||||
<input type="text" name="domain" class="form-control" id="service_url" placeholder="https://google.com">
|
<input type="text" name="domain" class="form-control" id="service_url" placeholder="https://google.com">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +104,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button type="submit" class="btn btn-success">Create Service</button>
|
<button type="submit" class="btn btn-success btn-block ">Create Service</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
2
main.go
2
main.go
|
@ -150,7 +150,7 @@ func mainProcess() {
|
||||||
fmt.Println("Core database was not found, Statup is not setup yet.")
|
fmt.Println("Core database was not found, Statup is not setup yet.")
|
||||||
RunHTTPServer()
|
RunHTTPServer()
|
||||||
}
|
}
|
||||||
go CheckServices()
|
CheckServices()
|
||||||
if !setupMode {
|
if !setupMode {
|
||||||
LoadPlugins()
|
LoadPlugins()
|
||||||
RunHTTPServer()
|
RunHTTPServer()
|
||||||
|
|
|
@ -173,7 +173,7 @@ func TestService_AvgTime(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
avg := service.AvgUptime()
|
avg := service.AvgUptime()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "100.00", avg)
|
assert.Equal(t, "100", avg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Online24(t *testing.T) {
|
func TestService_Online24(t *testing.T) {
|
||||||
|
@ -217,7 +217,7 @@ func TestService_Hits(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
hits, err := service.Hits()
|
hits, err := service.Hits()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 5, len(hits))
|
assert.Equal(t, 0, len(hits))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_LimitedHits(t *testing.T) {
|
func TestService_LimitedHits(t *testing.T) {
|
||||||
|
@ -225,7 +225,7 @@ func TestService_LimitedHits(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
hits, err := service.LimitedHits()
|
hits, err := service.LimitedHits()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 5, len(hits))
|
assert.Equal(t, 0, len(hits))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
|
|
13
services.go
13
services.go
|
@ -31,6 +31,7 @@ type Service struct {
|
||||||
AvgResponse string `json:"avg_response"`
|
AvgResponse string `json:"avg_response"`
|
||||||
TotalUptime string `json:"uptime"`
|
TotalUptime string `json:"uptime"`
|
||||||
Failures []*Failure `json:"failures"`
|
Failures []*Failure `json:"failures"`
|
||||||
|
Checkins []*Checkin `json:"checkins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceCol() db.Collection {
|
func serviceCol() db.Collection {
|
||||||
|
@ -41,6 +42,7 @@ func SelectService(id int64) (*Service, error) {
|
||||||
var service *Service
|
var service *Service
|
||||||
res := serviceCol().Find("id", id)
|
res := serviceCol().Find("id", id)
|
||||||
err := res.One(&service)
|
err := res.One(&service)
|
||||||
|
service.Checkins = service.SelectAllCheckins()
|
||||||
return service, err
|
return service, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,17 +50,12 @@ func SelectAllServices() ([]*Service, error) {
|
||||||
var services []*Service
|
var services []*Service
|
||||||
col := serviceCol().Find()
|
col := serviceCol().Find()
|
||||||
err := col.All(&services)
|
err := col.All(&services)
|
||||||
|
for _, s := range services {
|
||||||
|
s.Checkins = s.SelectAllCheckins()
|
||||||
|
}
|
||||||
return services, err
|
return services, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) FormatData() *Service {
|
|
||||||
s.GraphData()
|
|
||||||
s.AvgUptime()
|
|
||||||
s.Online24()
|
|
||||||
s.AvgTime()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) AvgTime() float64 {
|
func (s *Service) AvgTime() float64 {
|
||||||
total, _ := s.TotalHits()
|
total, _ := s.TotalHits()
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
|
|
95
setup.go
95
setup.go
|
@ -1,11 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/go-yaml/yaml"
|
"github.com/go-yaml/yaml"
|
||||||
"github.com/hunterlong/statup/plugin"
|
"github.com/hunterlong/statup/plugin"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -154,3 +156,96 @@ func (c *DbConfig) Save() error {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DropDatabase() {
|
||||||
|
fmt.Println("Dropping Tables...")
|
||||||
|
down, _ := sqlBox.String("down.sql")
|
||||||
|
requests := strings.Split(down, ";")
|
||||||
|
for _, request := range requests {
|
||||||
|
_, err := dbSession.Exec(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDatabase() {
|
||||||
|
fmt.Println("Creating Tables...")
|
||||||
|
sql := "postgres_up.sql"
|
||||||
|
if dbServer == "mysql" {
|
||||||
|
sql = "mysql_up.sql"
|
||||||
|
} else if dbServer == "sqlite3" {
|
||||||
|
sql = "sqlite_up.sql"
|
||||||
|
}
|
||||||
|
up, _ := sqlBox.String(sql)
|
||||||
|
requests := strings.Split(up, ";")
|
||||||
|
for _, request := range requests {
|
||||||
|
_, err := dbSession.Exec(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//secret := NewSHA1Hash()
|
||||||
|
//db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan()
|
||||||
|
fmt.Println("Database Created")
|
||||||
|
//SampleData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadSampleData() error {
|
||||||
|
fmt.Println("Inserting Sample Data...")
|
||||||
|
s1 := &Service{
|
||||||
|
Name: "Google",
|
||||||
|
Domain: "https://google.com",
|
||||||
|
ExpectedStatus: 200,
|
||||||
|
Interval: 10,
|
||||||
|
Port: 0,
|
||||||
|
Type: "https",
|
||||||
|
Method: "GET",
|
||||||
|
}
|
||||||
|
s2 := &Service{
|
||||||
|
Name: "Statup.io",
|
||||||
|
Domain: "https://statup.io",
|
||||||
|
ExpectedStatus: 200,
|
||||||
|
Interval: 15,
|
||||||
|
Port: 0,
|
||||||
|
Type: "https",
|
||||||
|
Method: "GET",
|
||||||
|
}
|
||||||
|
s3 := &Service{
|
||||||
|
Name: "Statup.io SSL Check",
|
||||||
|
Domain: "https://statup.io",
|
||||||
|
ExpectedStatus: 200,
|
||||||
|
Interval: 15,
|
||||||
|
Port: 443,
|
||||||
|
Type: "tcp",
|
||||||
|
}
|
||||||
|
s4 := &Service{
|
||||||
|
Name: "Github Failing Check",
|
||||||
|
Domain: "https://github.com/thisisnotausernamemaybeitis",
|
||||||
|
ExpectedStatus: 200,
|
||||||
|
Interval: 15,
|
||||||
|
Port: 0,
|
||||||
|
Type: "https",
|
||||||
|
Method: "GET",
|
||||||
|
}
|
||||||
|
s1.Create()
|
||||||
|
s2.Create()
|
||||||
|
s3.Create()
|
||||||
|
s4.Create()
|
||||||
|
|
||||||
|
checkin := &Checkin{
|
||||||
|
Service: s2.Id,
|
||||||
|
Interval: 30,
|
||||||
|
Api: NewSHA1Hash(18),
|
||||||
|
}
|
||||||
|
checkin.Create()
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
s1.Check()
|
||||||
|
s2.Check()
|
||||||
|
s3.Check()
|
||||||
|
s4.Check()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,4 +2,5 @@ DROP table core;
|
||||||
DROP table hits;
|
DROP table hits;
|
||||||
DROP table failures;
|
DROP table failures;
|
||||||
DROP table users;
|
DROP table users;
|
||||||
|
DROP table checkins;
|
||||||
DROP table services;
|
DROP table services;
|
|
@ -46,4 +46,13 @@ CREATE TABLE failures (
|
||||||
created_at TIMESTAMP,
|
created_at TIMESTAMP,
|
||||||
INDEX (id, service),
|
INDEX (id, service),
|
||||||
FOREIGN KEY (service) REFERENCES services(id)
|
FOREIGN KEY (service) REFERENCES services(id)
|
||||||
|
);
|
||||||
|
CREATE TABLE checkins (
|
||||||
|
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
service INTEGER NOT NULL,
|
||||||
|
check_interval integer,
|
||||||
|
api text,
|
||||||
|
created_at TIMESTAMP,
|
||||||
|
INDEX (id, service),
|
||||||
|
FOREIGN KEY (service) REFERENCES services(id)
|
||||||
);
|
);
|
|
@ -32,13 +32,6 @@ CREATE TABLE services (
|
||||||
created_at TIMESTAMP
|
created_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE checkins (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
check_interval integer,
|
|
||||||
created_at TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE hits (
|
CREATE TABLE hits (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
@ -54,5 +47,15 @@ CREATE TABLE failures (
|
||||||
created_at TIMESTAMP WITHOUT TIME zone
|
created_at TIMESTAMP WITHOUT TIME zone
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE checkins (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
check_interval integer,
|
||||||
|
api text,
|
||||||
|
created_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE INDEX idx_hits ON hits(service);
|
CREATE INDEX idx_hits ON hits(service);
|
||||||
CREATE INDEX idx_failures ON failures(service);
|
CREATE INDEX idx_failures ON failures(service);
|
||||||
|
CREATE INDEX idx_checkins ON checkins(service);
|
|
@ -46,5 +46,14 @@ CREATE TABLE failures (
|
||||||
created_at TIMESTAMP
|
created_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE checkins (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
check_interval integer,
|
||||||
|
api text,
|
||||||
|
created_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_hits ON hits(service);
|
CREATE INDEX idx_hits ON hits(service);
|
||||||
CREATE INDEX idx_failures ON failures(service);
|
CREATE INDEX idx_failures ON failures(service);
|
||||||
|
CREATE INDEX idx_checkins ON checkins(service);
|
21
web.go
21
web.go
|
@ -39,6 +39,7 @@ func Router() *mux.Router {
|
||||||
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
|
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
|
||||||
r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler))
|
r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler))
|
||||||
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
|
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
|
||||||
|
r.Handle("/service/{id}/checkin", http.HandlerFunc(CheckinCreateUpdateHandler)).Methods("POST")
|
||||||
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
|
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
|
||||||
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
|
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
|
||||||
r.Handle("/users/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
|
r.Handle("/users/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
|
||||||
|
@ -49,6 +50,7 @@ func Router() *mux.Router {
|
||||||
r.Handle("/help", http.HandlerFunc(HelpHandler))
|
r.Handle("/help", http.HandlerFunc(HelpHandler))
|
||||||
|
|
||||||
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
|
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
|
||||||
|
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
|
||||||
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
|
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
|
||||||
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")
|
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")
|
||||||
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceUpdateHandler)).Methods("POST")
|
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceUpdateHandler)).Methods("POST")
|
||||||
|
@ -335,6 +337,25 @@ func HelpHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ExecuteResponse(w, r, "help.html", nil)
|
ExecuteResponse(w, r, "help.html", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
auth := IsAuthenticated(r)
|
||||||
|
if !auth {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
interval := StringInt(r.PostForm.Get("interval"))
|
||||||
|
service, _ := SelectService(StringInt(vars["id"]))
|
||||||
|
checkin := &Checkin{
|
||||||
|
Service: service.Id,
|
||||||
|
Interval: interval,
|
||||||
|
Api: NewSHA1Hash(18),
|
||||||
|
}
|
||||||
|
checkin.Create()
|
||||||
|
fmt.Println(checkin.Create())
|
||||||
|
ExecuteResponse(w, r, "service.html", service)
|
||||||
|
}
|
||||||
|
|
||||||
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
//auth := IsAuthenticated(r)
|
//auth := IsAuthenticated(r)
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue