pull/10/head
Hunter Long 2018-06-21 21:02:57 -07:00
parent 7705087345
commit b4fe30c2cc
26 changed files with 472 additions and 177 deletions

4
.gitignore vendored
View File

@ -3,4 +3,6 @@ rice-box.go
config.yml
statup.db
plugins/*.so
data
data
build
vendor

View File

@ -45,6 +45,9 @@ notifications:
before_install:
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then travis_wait 30 docker pull karalabe/xgo-latest; fi
after_success:
- curl -s -X POST $DOCKER > /dev/null
before_script:
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
- psql -c 'create database test;' -U postgres

139
Gopkg.lock generated Normal file
View File

@ -0,0 +1,139 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/GeertJohan/go.rice"
packages = [
".",
"embedded"
]
revision = "c02ca9a983da5807ddf7d796784928f5be4afd09"
[[projects]]
branch = "master"
name = "github.com/daaku/go.zipexe"
packages = ["."]
revision = "a5fe2436ffcb3236e175e5149162b41cd28bd27d"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/fatih/structs"
packages = ["."]
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
version = "v1.0"
[[projects]]
name = "github.com/go-sql-driver/mysql"
packages = ["."]
revision = "d523deb1b23d913de5bdada721a6071e71283618"
version = "v1.4.0"
[[projects]]
name = "github.com/go-yaml/yaml"
packages = ["."]
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
name = "github.com/gorilla/securecookie"
packages = ["."]
revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/sessions"
packages = ["."]
revision = "03b6f63cc43ef9c7240a635a5e22b13180e822b8"
version = "v1.1.1"
[[projects]]
branch = "master"
name = "github.com/kardianos/osext"
packages = ["."]
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [
".",
"oid"
]
revision = "90697d60dd844d5ef6ff15135d0203f65d2f53b8"
[[projects]]
name = "github.com/mattn/go-sqlite3"
packages = ["."]
revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4"
version = "v1.9.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"bcrypt",
"blowfish"
]
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
[[projects]]
name = "google.golang.org/appengine"
packages = ["cloudsql"]
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
version = "v1.1.0"
[[projects]]
name = "upper.io/db.v3"
packages = [
".",
"internal/cache",
"internal/cache/hashstructure",
"internal/immutable",
"internal/sqladapter",
"internal/sqladapter/compat",
"internal/sqladapter/exql",
"lib/reflectx",
"lib/sqlbuilder",
"mysql",
"postgresql",
"sqlite"
]
revision = "d90922beee6de3f39c93ed677f6da82565d07154"
version = "v3.5.3"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d8ed5645d6f4e746646932baa40deb382ea017a84594ec46fda94538f4d00db2"
solver-name = "gps-cdcl"
solver-version = 1

62
Gopkg.toml Normal file
View File

@ -0,0 +1,62 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# 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]]
branch = "master"
name = "github.com/GeertJohan/go.rice"
[[constraint]]
name = "github.com/fatih/structs"
version = "1.0.0"
[[constraint]]
name = "github.com/go-yaml/yaml"
version = "2.2.1"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.2"
[[constraint]]
name = "github.com/gorilla/sessions"
version = "1.1.1"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.2"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
[[constraint]]
name = "upper.io/db.v3"
version = "3.5.3"
[prune]
go-tests = true
unused-packages = true

View File

@ -26,6 +26,15 @@ func (c *Core) Update() (*Core, error) {
return c, nil
}
func (c Core) AllOnline() bool {
for _, s := range services {
if !s.Online {
return false
}
}
return true
}
func SelectCore() (*Core, error) {
var core Core
err := dbSession.Collection("core").Find().One(&core)

View File

@ -8,11 +8,15 @@ import (
func (f *Failure) ParseError() string {
err := strings.Contains(f.Issue, "operation timed out")
if err {
return fmt.Sprintf("HTTP Request timed out after x seconds")
return fmt.Sprintf("HTTP Request Timed Out")
}
err = strings.Contains(f.Issue, "x509: certificate is valid")
if err {
return fmt.Sprintf("SSL Certificate invalid")
}
err = strings.Contains(f.Issue, "no such host")
if err {
return fmt.Sprintf("Domain is offline or not found")
}
return f.Issue
}

View File

@ -1,6 +1,7 @@
package main
import (
"github.com/ararog/timeago"
"time"
)
@ -9,7 +10,6 @@ type Failure struct {
Issue string `db:"issue"`
Service int64 `db:"service"`
CreatedAt time.Time `db:"created_at"`
Ago string
}
func (s *Service) CreateFailure(data FailureData) (int64, error) {
@ -29,18 +29,32 @@ func (s *Service) CreateFailure(data FailureData) (int64, error) {
func (s *Service) SelectAllFailures() ([]*Failure, error) {
var fails []*Failure
col := dbSession.Collection("failures").Find("service", s.Id)
col := dbSession.Collection("failures").Find("service", s.Id).OrderBy("-id")
err := col.All(&fails)
return fails, err
}
func (u *Service) DeleteFailures() {
var fails []*Failure
col := dbSession.Collection("failures")
col.Find("service", u.Id).All(&fails)
for _, fail := range fails {
fail.Delete()
}
}
func (s *Service) LimitedFailures() []*Failure {
var fails []*Failure
col := dbSession.Collection("failures").Find("service", s.Id).Limit(10)
col.All(&fails)
col := dbSession.Collection("failures").Find("service", s.Id)
col.OrderBy("-id").Limit(10).All(&fails)
return fails
}
func (f *Failure) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
return got
}
func (f *Failure) Delete() error {
col := dbSession.Collection("failures").Find("id", f.Id)
return col.Delete()

12
go.mod
View File

@ -1,12 +0,0 @@
module github.com/hunterlong/statup
require (
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
github.com/gorilla/sessions v1.1.1
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
github.com/lib/pq v0.0.0-20180523175426-90697d60dd84
golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405
)

25
hits.go
View File

@ -1,6 +1,9 @@
package main
import "time"
import (
"time"
"upper.io/db.v3"
)
type Hit struct {
Id int `db:"id,omitempty"`
@ -9,14 +12,17 @@ type Hit struct {
CreatedAt time.Time `db:"created_at"`
}
func hitCol() db.Collection {
return dbSession.Collection("hits")
}
func (s *Service) CreateHit(d HitData) (int64, error) {
h := Hit{
Service: s.Id,
Latency: d.Latency,
CreatedAt: time.Now(),
}
col := dbSession.Collection("hits")
uuid, err := col.Insert(h)
uuid, err := hitCol().Insert(h)
if uuid == nil {
return 0, err
}
@ -25,20 +31,27 @@ func (s *Service) CreateHit(d HitData) (int64, error) {
func (s *Service) Hits() ([]Hit, error) {
var hits []Hit
col := dbSession.Collection("hits").Find("service", s.Id)
col := hitCol().Find("service", s.Id).OrderBy("-id")
err := col.All(&hits)
return hits, err
}
func (s *Service) LimitedHits() ([]Hit, error) {
var hits []Hit
col := hitCol().Find("service", s.Id).Limit(256).OrderBy("-id")
err := col.All(&hits)
return hits, err
}
func (s *Service) SelectHitsGroupBy(group string) ([]Hit, error) {
var hits []Hit
col := dbSession.Collection("hits").Find("service", s.Id)
col := hitCol().Find("service", s.Id)
err := col.All(&hits)
return hits, err
}
func (s *Service) TotalHits() (uint64, error) {
col := dbSession.Collection("hits").Find("service", s.Id)
col := hitCol().Find("service", s.Id)
amount, err := col.Count()
return amount, err
}

View File

@ -4,16 +4,12 @@ HTML,BODY {
}
.container {
max-width: 790px;
background-color: white;
padding: 50px;
border-radius: 7px;
padding-top: 20px;
padding-bottom: 20px;
}
.navbar {
margin-left: -50px;
margin-top: -50px;
width: 790px;
margin-bottom: 30px;
}
@ -54,7 +50,7 @@ HTML,BODY {
}
.online_list {
font-size: 1.5rem;
}
.footer {
@ -90,17 +86,13 @@ HTML,BODY {
overflow: hidden;
}
.card-body H3 A {
color: #424242;
.card-body H4 A {
color: #239e07;
text-decoration: none;
}
@media (max-width: 767px) {
HTML,BODY {
background-color: #efefef;
margin: 0px 0;
}
.container {
padding: 0px;
}
@ -117,7 +109,7 @@ HTML,BODY {
}
.lg_number {
font-size: 22pt;
font-size: 1.2rem;
}

View File

@ -12,10 +12,12 @@
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{template "nav"}}
<div class="col-12">
<div class="row stats_area mb-4">
<div class="col-4">
@ -57,10 +59,12 @@
</div>
</div>
</div>
</div>
{{template "footer"}}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>

8
html/tmpl/footer.html Normal file
View File

@ -0,0 +1,8 @@
{{ define "footer"}}
<div class="footer text-center">
{{ if .Core.Footer }}
{{ safe .Core.Footer }}
{{ end }}
<a href="https://statup.io" target="_blank">Statup made with ❤️</a> | <a href="/dashboard">Dashboard</a>
</div>
{{ end }}

View File

@ -11,15 +11,12 @@
</head>
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{if Auth}}
{{template "nav"}}
{{end}}
<div class="row">
<div class="col-12">
<h2>Statup v{{ VERSION }} Help</h2>
@ -51,8 +48,6 @@
</div>
</div>
</div>
<script src="/js/jquery-3.3.1.slim.min.js"></script>

View File

@ -13,32 +13,42 @@
<body>
<div class="container col-md-7 col-sm-12 mt-2">
<h1 class="text-center mb-4 mt-sm-3">{{.Core.Name}}</h1>
{{ if .Core.Description }}
<h5 class="text-center mb-1 mt-sm-2">{{ .Core.Description }}</h5>
<h5 class="text-center mb-5 text-muted">{{ .Core.Description }}</h5>
{{ end }}
<div class="container">
<div class="col-12 mb-5">
<div class="col-12 mb-5">
<div class="list-group online_list">
{{ range .Services }}
<a href="#" class="list-group-item list-group-item-action {{if .Online}}{{ end }}">
{{ .Name }}
{{if .Online}}
<span class="badge online_badge float-right">ONLINE</span>
{{ else }}
<span class="badge offline_badge float-right">OFFLINE</span>
{{end}}
</a>
{{ end }}
{{ 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">
{{ range .Services }}
<a href="#" class="list-group-item list-group-item-action {{if .Online}}{{ end }}">
{{ .Name }}
{{if .Online}}
<span class="badge online_badge float-right">ONLINE</span>
{{ else }}
<span class="badge offline_badge float-right">OFFLINE</span>
{{end}}
</a>
{{ end }}
</div>
</div>
<div class="col-12">
{{ range .Services }}
@ -50,26 +60,26 @@
<div class="progress-bar {{if .Online24Hours}} bg-success {{else}} bg-danger {{end}}" role="progressbar" style="width: {{.Online24Hours}}%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<h3 class="mt-4"><a href="/service/{{.Id}}">{{ .Name }}</a>
<h4 class="mt-4"><a href="/service/{{.Id}}">{{ .Name }}</a>
{{if .Online}}
<span class="badge online_badge float-right">ONLINE</span>
{{ else }}
<span class="badge offline_badge float-right">OFFLINE</span>
{{end}}</h3>
{{end}}</h4>
<div class="row stats_area mt-5 mb-5">
<div class="col-4">
<div class="col-md-4 col-sm-12">
<span class="lg_number">{{.Online24}}%</span>
Online last 24 Hours
</div>
<div class="col-4">
<div class="col-md-4 col-sm-6">
<span class="lg_number">{{.AvgTime}}ms</span>
Average Response
</div>
<div class="col-4">
<div class="col-md-4 col-sm-6">
<span class="lg_number">{{.AvgUptime}}%</span>
Total Uptime
</div>
@ -77,12 +87,19 @@
<canvas id="service_{{ .Id }}" width="400" height="120"></canvas>
{{ if .LimitedFailures }}
<div class="list-group mt-5">
{{ range .LimitedFailures }}
<blockquote class="blockquote text-right mt-3">
<p class="mb-0">{{.ParseError}}</p>
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
</blockquote>
{{ end }}
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<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 }}
</div>
</div>
@ -93,15 +110,11 @@
</div>
<div class="footer text-center">
{{ if .Core.Footer }}
{{ safe .Core.Footer }}
{{ end }}
<a href="https://statup.io" target="_blank">Statup made with ❤️</a> | <a href="/dashboard">Dashboard</a>
</div>
{{template "footer"}}
<script>
{{ range .Services }}
{{if .GraphData}}
var ctx = document.getElementById("service_{{.Id}}").getContext('2d');
var chartdata = new Chart(ctx, {
@ -158,6 +171,7 @@ var chartdata = new Chart(ctx, {
}
});
{{ end }}
{{ end }}
</script>

View File

@ -12,12 +12,16 @@
<body>
<div class="container">
<div class="row">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
<div class="col-12">
{{ if not . }}
<div class="alert alert-danger" role="alert">
Incorrect login information submitted, try again.
</div>
{{ end }}
<form action="/dashboard" method="POST">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label>
@ -51,11 +55,10 @@
</div>
</div>
</div>
{{template "footer"}}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body>

View File

@ -12,7 +12,7 @@
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{template "nav"}}
@ -105,6 +105,8 @@
</div>
{{template "footer"}}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>

View File

@ -12,14 +12,13 @@
</head>
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{if Auth}}
{{template "nav"}}
{{end}}
<div class="row">
<div class="col-12">
<div class="col-12">
<div class="col-12 mb-4">
@ -57,9 +56,8 @@
</blockquote>
{{ end }}
</div>
</div>
</div>
{{if Auth}}
@ -69,44 +67,61 @@
<form action="/service/{{.Id}}" method="POST">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-4 col-form-label">Service Name</label>
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" value="{{.Name}}" id="inputEmail3">
<input type="text" name="name" class="form-control" id="service_name" value="{{.Name}}" placeholder="Name">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<label for="service_type" class="col-sm-4 col-form-label">Service Check Type</label>
<div class="col-sm-8">
<input type="text" name="domain" class="form-control" value="{{.Domain}}" id="inputPassword3">
<select name="check_type" class="form-control" id="service_type" value="{{.Type}}">
<option value="http" selected>HTTP Service</option>
<option value="tcp">TCP Service</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<input type="text" name="expected" class="form-control" value="{{.Expected}}" id="inputPassword3">
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<div class="col-8">
<input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Status Code</label>
<label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label>
<div class="col-sm-8">
<input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="inputPassword3">
<select name="method" class="form-control" id="service_check_type" value="{{.Method}}">
<option value="GET" selected>GET</option>
<option value="POST">POST</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">HTTP Method</label>
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<input type="text" name="method" class="form-control" value="{{.Method}}" id="inputPassword3">
<textarea name="expected" class="form-control" id="service_response" rows="3">{{.Expected}}</textarea>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" value="{{.Interval}}" id="inputPassword3">
<input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="service_response_code" value="200">
</div>
</div>
<div class="form-group row">
<label for="service_port" class="col-sm-4 col-form-label">TCP Port</label>
<div class="col-sm-8">
<input type="number" name="port" class="form-control" value="{{.Port}}" id="service_port" placeholder="8080">
</div>
</div>
<div class="form-group row">
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" value="{{.Interval}}" id="service_interval" placeholder="10">
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<a class="btn btn-primary">Test</a>
<button type="submit" class="btn btn-success">Update Service</button>
<a href="/service/{{ .Id }}/delete_failures" class="btn btn-danger">Delete All Failures</a>
</div>
@ -127,6 +142,7 @@
</div>
</div>
{{template "footer"}}
<script>
var ctx = document.getElementById("service").getContext('2d');

View File

@ -12,13 +12,11 @@
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{template "nav"}}
<div class="row">
<div class="col-12">
<div class="col-12">
<h3>Services</h3>
@ -48,53 +46,66 @@
</tbody>
</table>
</div>
<div class="col-12">
<h3>Create Service</h3>
<form action="/services" method="POST">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-4 col-form-label">Service Name</label>
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" id="inputEmail3" placeholder="Name">
<input type="text" name="name" class="form-control" id="service_name" placeholder="Name">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<label for="service_type" class="col-sm-4 col-form-label">Service Check Type</label>
<div class="col-sm-8">
<input type="text" name="domain" class="form-control" id="inputPassword3" placeholder="https://google.com">
<select name="check_type" class="form-control" id="service_type">
<option value="http" selected>HTTP Service</option>
<option value="tcp">TCP Service</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<input type="text" name="expected" class="form-control" id="inputPassword3" placeholder="string">
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<div class="col-8">
<input type="text" name="domain" class="form-control" id="service_url" placeholder="https://google.com">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Status Code</label>
<label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label>
<div class="col-sm-8">
<input type="number" name="expected_status" class="form-control" id="inputPassword3" placeholder="200">
<select name="method" class="form-control" id="service_check_type">
<option value="GET" selected>GET</option>
<option value="POST">POST</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">HTTP Method</label>
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<input type="text" name="method" class="form-control" id="inputPassword3" placeholder="GET">
<textarea name="expected" class="form-control" id="service_response" rows="3"></textarea>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" id="inputPassword3" placeholder="10">
<input type="number" name="expected_status" class="form-control" id="service_response_code" value="200">
</div>
</div>
<div class="form-group row">
<label for="service_port" class="col-sm-4 col-form-label">TCP Port</label>
<div class="col-sm-8">
<input type="number" name="port" class="form-control" id="service_port" placeholder="8080">
</div>
</div>
<div class="form-group row">
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" id="service_interval" placeholder="10">
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<a class="btn btn-primary">Test</a>
<button type="submit" class="btn btn-success">Create Service</button>
</div>
</div>
@ -104,8 +115,7 @@
</div>
</div>
{{template "footer"}}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>

View File

@ -13,7 +13,7 @@
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{ if .Error }}
<div class="alert alert-danger" role="alert">
@ -102,6 +102,8 @@
</div>
{{template "footer"}}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/setup.js"></script>

View File

@ -12,12 +12,10 @@
<body>
<div class="container">
<div class="container col-md-7 col-sm-12 mt-2 bg-light">
{{template "nav"}}
<div class="row">
<div class="col-12">
<h3>Users</h3>
@ -74,10 +72,10 @@
</div>
</div>
</div>
{{template "footer"}}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body>

View File

@ -168,14 +168,6 @@ func TestService_Check(t *testing.T) {
assert.Equal(t, true, out.Online)
}
func TestService_Hits(t *testing.T) {
service, err := SelectService(1)
assert.Nil(t, err)
hits, err := service.Hits()
assert.Nil(t, err)
assert.Equal(t, 0, len(hits))
}
func TestService_AvgTime(t *testing.T) {
service, err := SelectService(1)
assert.Nil(t, err)
@ -193,7 +185,6 @@ func TestService_Online24(t *testing.T) {
}
func TestService_GraphData(t *testing.T) {
t.SkipNow()
service, err := SelectService(1)
assert.Nil(t, err)
data := service.GraphData()
@ -221,6 +212,22 @@ func TestBadService_Check(t *testing.T) {
assert.Equal(t, "Github Failing Check", service.Name)
}
func TestService_Hits(t *testing.T) {
service, err := SelectService(1)
assert.Nil(t, err)
hits, err := service.Hits()
assert.Nil(t, err)
assert.Equal(t, 5, len(hits))
}
func TestService_LimitedHits(t *testing.T) {
service, err := SelectService(1)
assert.Nil(t, err)
hits, err := service.LimitedHits()
assert.Nil(t, err)
assert.Equal(t, 5, len(hits))
}
func Test(t *testing.T) {
var err error
configs, err = LoadConfig()

View File

@ -69,5 +69,5 @@ type Routing struct {
type Info struct {
Name string
Description string
Form string
Form string
}

View File

@ -7,6 +7,7 @@ import (
"math/rand"
"strconv"
"time"
"upper.io/db.v3"
)
var (
@ -32,17 +33,20 @@ type Service struct {
Failures []*Failure `json:"failures"`
}
func serviceCol() db.Collection {
return dbSession.Collection("services")
}
func SelectService(id int64) (*Service, error) {
var service *Service
col := dbSession.Collection("services")
res := col.Find("id", id)
res := serviceCol().Find("id", id)
err := res.One(&service)
return service, err
}
func SelectAllServices() ([]*Service, error) {
var services []*Service
col := dbSession.Collection("services").Find()
col := serviceCol().Find()
err := col.All(&services)
return services, err
}
@ -95,7 +99,7 @@ type GraphJson struct {
func (s *Service) GraphData() string {
var d []GraphJson
hits, _ := s.Hits()
hits, _ := s.LimitedHits()
for _, h := range hits {
val := h.CreatedAt
o := GraphJson{
@ -112,7 +116,7 @@ func (s *Service) AvgUptime() string {
failed, _ := s.TotalFailures()
total, _ := s.TotalHits()
if failed == 0 {
s.TotalUptime = "100.00"
s.TotalUptime = "100"
return s.TotalUptime
}
if total == 0 {
@ -125,35 +129,26 @@ func (s *Service) AvgUptime() string {
percent = 0
}
s.TotalUptime = fmt.Sprintf("%0.2f", percent)
if s.TotalUptime == "100.00" {
s.TotalUptime = "100"
}
return s.TotalUptime
}
func (u *Service) Delete() error {
col := dbSession.Collection("services")
res := col.Find("id", u.Id)
res := serviceCol().Find("id", u.Id)
err := res.Delete()
OnDeletedService(u)
return err
}
func (u *Service) DeleteFailures() {
var fails []*Failure
col := dbSession.Collection("failures")
col.Find("service", u.Id).All(&fails)
for _, fail := range fails {
fail.Delete()
}
}
func (u *Service) Update() {
OnUpdateService(u)
}
func (u *Service) Create() (int64, error) {
u.CreatedAt = time.Now()
col := dbSession.Collection("services")
uuid, err := col.Insert(u)
uuid, err := serviceCol().Insert(u)
services, _ = SelectAllServices()
if uuid == nil {
return 0, err

View File

@ -102,6 +102,15 @@ func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
ExecuteResponse(w, r, "setup.html", a)
}
func (c *DbConfig) Clean() *DbConfig {
if os.Getenv("DB_PORT") != "" {
if c.DbConn == "postgres" {
c.DbHost = c.DbHost + ":" + os.Getenv("DB_PORT")
}
}
return c
}
func (c *DbConfig) Save() error {
var err error
config, err := os.Create("config.yml")

View File

@ -32,6 +32,13 @@ CREATE TABLE services (
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 (
id SERIAL PRIMARY KEY,
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
@ -42,6 +49,7 @@ CREATE TABLE hits (
CREATE TABLE failures (
id SERIAL PRIMARY KEY,
issue text,
method text,
service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE,
created_at TIMESTAMP WITHOUT TIME zone
);

24
web.go
View File

@ -7,10 +7,10 @@ import (
"github.com/gorilla/sessions"
"html/template"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"regexp"
)
var (
@ -98,9 +98,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
session.Save(r, w)
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
w.WriteHeader(502)
w.Header().Set("Content-Type", "plain/text")
fmt.Fprintln(w, "bad")
ExecuteResponse(w, r, "login.html", auth)
}
}
@ -129,8 +127,8 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
expected := r.PostForm.Get("expected")
status, _ := strconv.Atoi(r.PostForm.Get("expected_status"))
interval, _ := strconv.Atoi(r.PostForm.Get("interval"))
fmt.Println(r.PostForm)
port, _ := strconv.Atoi(r.PostForm.Get("port"))
checkType := r.PostForm.Get("check_type")
service := Service{
Name: name,
@ -139,10 +137,10 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
Expected: expected,
ExpectedStatus: status,
Interval: interval,
Type: checkType,
Port: port,
}
fmt.Println(service)
_, err := service.Create()
if err != nil {
go service.CheckQueue()
@ -285,7 +283,7 @@ func PluginsHandler(w http.ResponseWriter, r *http.Request) {
for _, p := range allPlugins {
fields := structs.Map(p.GetInfo())
pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(),fields})
pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(), fields})
}
core.PluginFields = pluginFields
@ -294,7 +292,7 @@ func PluginsHandler(w http.ResponseWriter, r *http.Request) {
type PluginSelect struct {
Plugin string
Form string
Form string
Params map[string]interface{}
}
@ -375,6 +373,7 @@ func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
nav, _ := tmplBox.String("nav.html")
footer, _ := tmplBox.String("footer.html")
render, err := tmplBox.String(file)
if err != nil {
panic(err)
@ -398,6 +397,7 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
},
})
t, _ = t.Parse(nav)
t, _ = t.Parse(footer)
t.Parse(render)
t.Execute(w, data)
}
@ -431,8 +431,6 @@ func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/users", http.StatusSeeOther)
}
func UnderScoreString(str string) string {
// convert every letter to lower case
@ -454,4 +452,4 @@ func UnderScoreString(str string) string {
newStr = strings.TrimSuffix(newStr, "_")
return newStr
}
}