db changes

pull/429/head
Hunter Long 2020-03-06 01:33:46 -08:00
parent 66ec613953
commit 7dc285295b
36 changed files with 221 additions and 126 deletions

View File

@ -45,6 +45,12 @@ stop:
logs: logs:
docker logs statping --follow docker logs statping --follow
db-up:
docker-compose -f dev/docker-compose.db.yml up -d --remove-orphans
db-down:
docker-compose -f dev/docker-compose.full.yml down --remove-orphans
console: console:
docker exec -t -i statping /bin/sh docker exec -t -i statping /bin/sh

View File

@ -104,17 +104,22 @@ type Database interface {
Since(time.Time) Database Since(time.Time) Database
Between(time.Time, time.Time) Database Between(time.Time, time.Time) Database
SelectByTime(string) string SelectByTime(time.Duration) string
MultipleSelects(args ...string) Database MultipleSelects(args ...string) Database
FormatTime(t time.Time) string FormatTime(t time.Time) string
ParseTime(t string) (time.Time, error) ParseTime(t string) (time.Time, error)
DbType() string
} }
func DB() Database { func DB() Database {
return database return database
} }
func (it *Db) DbType() string {
return it.Database.Dialect().GetName()
}
func Close() error { func Close() error {
if database == nil { if database == nil {
return nil return nil
@ -145,11 +150,21 @@ func Available() bool {
return true return true
} }
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("service = ?", 1000)
}
func (it *Db) MultipleSelects(args ...string) Database { func (it *Db) MultipleSelects(args ...string) Database {
joined := strings.Join(args, ", ") joined := strings.Join(args, ", ")
return it.Select(joined) return it.Select(joined)
} }
func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
}
}
type Db struct { type Db struct {
Database *gorm.DB Database *gorm.DB
Type string Type string

View File

@ -28,7 +28,7 @@ func (b By) String() string {
type GroupQuery struct { type GroupQuery struct {
Start time.Time Start time.Time
End time.Time End time.Time
Group string Group time.Duration
Order string Order string
Limit int Limit int
Offset int Offset int
@ -47,8 +47,15 @@ func (b GroupQuery) Database() Database {
var ( var (
ByCount = By("COUNT(id) as amount") ByCount = By("COUNT(id) as amount")
ByAverage = func(column string) By { ByAverage = func(column string, multiplier int) By {
return By(fmt.Sprintf("AVG(%s) as amount", column)) switch database.DbType() {
case "mysql":
return By(fmt.Sprintf("CAST(AVG(%s)*%d as UNSIGNED) as amount", column, multiplier))
case "postgres":
return By(fmt.Sprintf("cast(AVG(%s)*%d as int) as amount", column, multiplier))
default:
return By(fmt.Sprintf("cast(AVG(%s)*%d as int) as amount", column, multiplier))
}
} }
) )
@ -76,7 +83,7 @@ func (g *GroupQuery) toFloatRows() []*TimeValue {
fmt.Println("float rows: ", timeframe, amount) fmt.Println("float rows: ", timeframe, amount)
newTs := types.FixedTime(timeframe, g.duration()) newTs := types.FixedTime(timeframe, g.Group)
data = append(data, &TimeValue{ data = append(data, &TimeValue{
Timeframe: newTs, Timeframe: newTs,
Amount: amount, Amount: amount,
@ -91,7 +98,7 @@ func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
dbQuery := g.db.MultipleSelects( dbQuery := g.db.MultipleSelects(
g.db.SelectByTime(g.Group), g.db.SelectByTime(g.Group),
by.String(), by.String(),
).Group("timeframe") ).Group("timeframe").Debug()
g.db = dbQuery g.db = dbQuery
@ -119,7 +126,7 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
log.Error(err, timeframe) log.Error(err, timeframe)
} }
trueTime, _ := g.db.ParseTime(timeframe) trueTime, _ := g.db.ParseTime(timeframe)
newTs := types.FixedTime(trueTime, g.duration()) newTs := types.FixedTime(trueTime, g.Group)
data = append(data, &TimeValue{ data = append(data, &TimeValue{
Timeframe: newTs, Timeframe: newTs,
Amount: amount, Amount: amount,
@ -131,12 +138,12 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) { func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) {
timeMap := make(map[string]float64) timeMap := make(map[string]float64)
var validSet []*TimeValue var validSet []*TimeValue
dur := t.g.duration() dur := t.g.Group
for _, v := range t.data { for _, v := range t.data {
timeMap[v.Timeframe] = v.Amount timeMap[v.Timeframe] = v.Amount
} }
currentStr := types.FixedTime(current, t.g.duration()) currentStr := types.FixedTime(current, t.g.Group)
for { for {
var amount float64 var amount float64
@ -151,31 +158,12 @@ func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) {
break break
} }
current = current.Add(dur) current = current.Add(dur)
currentStr = types.FixedTime(current, t.g.duration()) currentStr = types.FixedTime(current, t.g.Group)
} }
return validSet, nil return validSet, nil
} }
func (g *GroupQuery) duration() time.Duration {
switch g.Group {
case "second":
return types.Second
case "minute":
return types.Minute
case "hour":
return types.Hour
case "day":
return types.Day
case "month":
return types.Month
case "year":
return types.Year
default:
return types.Hour
}
}
type isObject interface { type isObject interface {
Db() Database Db() Database
} }
@ -183,9 +171,6 @@ type isObject interface {
func ParseQueries(r *http.Request, o isObject) *GroupQuery { func ParseQueries(r *http.Request, o isObject) *GroupQuery {
fields := parseGet(r) fields := parseGet(r)
grouping := fields.Get("group") grouping := fields.Get("group")
if grouping == "" {
grouping = "hour"
}
startField := utils.ToInt(fields.Get("start")) startField := utils.ToInt(fields.Get("start"))
endField := utils.ToInt(fields.Get("end")) endField := utils.ToInt(fields.Get("end"))
limit := utils.ToInt(fields.Get("limit")) limit := utils.ToInt(fields.Get("limit"))
@ -198,10 +183,19 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
db := o.Db() db := o.Db()
if grouping == "" {
grouping = "1h"
}
groupDur, err := time.ParseDuration(grouping)
if err != nil {
log.Errorln(err)
groupDur = 1 * time.Hour
}
query := &GroupQuery{ query := &GroupQuery{
Start: time.Unix(startField, 0).UTC(), Start: time.Unix(startField, 0).UTC(),
End: time.Unix(endField, 0).UTC(), End: time.Unix(endField, 0).UTC(),
Group: grouping, Group: groupDur,
Order: orderBy, Order: orderBy,
Limit: int(limit), Limit: int(limit),
Offset: int(offset), Offset: int(offset),
@ -210,11 +204,14 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
} }
if startField == 0 { if startField == 0 {
query.Start = time.Now().Add(-3 * types.Month).UTC() query.Start = time.Now().Add(-7 * types.Day).UTC()
} }
if endField == 0 { if endField == 0 {
query.End = time.Now().UTC() query.End = time.Now().UTC()
} }
if query.End.After(utils.Now()) {
query.End = utils.Now()
}
if query.Limit != 0 { if query.Limit != 0 {
db = db.Limit(query.Limit) db = db.Limit(query.Limit)

View File

@ -30,14 +30,15 @@ func (it *Db) FormatTime(t time.Time) string {
} }
} }
func (it *Db) SelectByTime(increment string) string { func (it *Db) SelectByTime(increment time.Duration) string {
seconds := int(increment.Seconds())
switch it.Type { switch it.Type {
case "mysql": case "mysql":
return fmt.Sprintf("CONCAT(date_format(created_at, '%s')) AS timeframe", it.correctTimestamp(increment)) return fmt.Sprintf("FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(created_at) / %d) * %d) AS timeframe", seconds, seconds)
case "postgres": case "postgres":
return fmt.Sprintf("date_trunc('%s', created_at) AS timeframe", increment) return fmt.Sprintf("date_trunc('%s', created_at) AS timeframe", increment)
default: default:
return fmt.Sprintf("strftime('%s', created_at, 'utc') as timeframe", it.correctTimestamp(increment)) return fmt.Sprintf("datetime((strftime('%%s', created_at) / %d) * %d, 'unixepoch') as timeframe", seconds, seconds)
} }
} }

View File

@ -29,7 +29,7 @@ services:
MYSQL_ROOT_PASSWORD: password123 MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping MYSQL_DATABASE: statping
MYSQL_USER: root MYSQL_USER: root
MYSQL_PASSWORD: password MYSQL_PASSWORD: password123
ports: ports:
- 3306:3306 - 3306:3306
healthcheck: healthcheck:

6
dev/prometheus.yml vendored
View File

@ -3,6 +3,12 @@ global:
evaluation_interval: 15s evaluation_interval: 15s
scrape_configs: scrape_configs:
- job_name: 'statping_local'
scrape_interval: 15s
bearer_token: 'samplesecret'
static_configs:
- targets: ['docker0:8585']
- job_name: 'statping' - job_name: 'statping'
scrape_interval: 15s scrape_interval: 15s
bearer_token: 'exampleapisecret' bearer_token: 'exampleapisecret'

View File

@ -36,12 +36,12 @@ class Api {
return axios.post('/api/services/' + data.id, data).then(response => (response.data)) return axios.post('/api/services/' + data.id, data).then(response => (response.data))
} }
async service_hits(id, start, end, group) { async service_hits(id, start, end, group, fill=true) {
return axios.get('/api/services/' + id + '/hits_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=true').then(response => (response.data)) return axios.get('/api/services/' + id + '/hits_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
} }
async service_failures_data(id, start, end, group) { async service_failures_data(id, start, end, group, fill=true) {
return axios.get('/api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=true').then(response => (response.data)) return axios.get('/api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
} }
async service_heatmap(id, start, end, group) { async service_heatmap(id, start, end, group) {

View File

@ -539,4 +539,26 @@ input.inputTags-field:focus {
cursor: pointer; cursor: pointer;
} }
.list-group-item {
min-height: 85pt;
}
.index_container {
min-height: 980pt;
}
/* Enter and leave animations can use different */
/* durations and timing functions. */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
@import 'mobile'; @import 'mobile';

View File

@ -17,7 +17,8 @@
.container { .container {
padding: 0px !important; padding: 0px !important;
padding-top: 15px !important; padding-top: 4vh !important;
min-height: 960pt;
} }
.group_header { .group_header {
@ -29,6 +30,9 @@
margin-top: 0px; margin-top: 0px;
width: 100%; width: 100%;
margin-bottom: 0; margin-bottom: 0;
box-shadow: 1px 10px 20px 0px;
height: 55pt;
color: rgba(0, 0, 0, 0.20);
} }
.btn-sm { .btn-sm {

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="col-12"> <div class="col-12">
<h1 class="text-black-50">Messages</h1> <h3 class="text-black-50">Messages</h3>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>

View File

@ -1,11 +1,11 @@
<template> <template>
<div> <div>
<div class="col-12"> <div class="col-12">
<h1 class="text-black-50">Services <h3 class="text-black-50">Services
<router-link to="/dashboard/create_service" class="btn btn-outline-success mt-1 float-right"> <router-link to="/dashboard/create_service" class="btn btn-outline-success mt-1 float-right">
<font-awesome-icon icon="plus"/> Create <font-awesome-icon icon="plus"/> Create
</router-link> </router-link>
</h1> </h3>
<ServicesList/> <ServicesList/>

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="col-12"> <div class="col-12">
<h1 class="text-black-50">Users</h1> <h3 class="text-black-50">Users</h3>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th scope="col">Username</th> <th scope="col">Username</th>
<th scope="col">Type</th> <th scope="col">Type</th>
<th scope="col">Last Login</th> <th scope="col" class="d-none d-md-table-cell">Last Login</th>
<th scope="col"></th> <th scope="col"></th>
</tr> </tr>
</thead> </thead>
@ -16,7 +16,7 @@
<td>{{user.username}}</td> <td>{{user.username}}</td>
<td v-if="user.admin"><span class="badge badge-danger">ADMIN</span></td> <td v-if="user.admin"><span class="badge badge-danger">ADMIN</span></td>
<td v-if="!user.admin"><span class="badge badge-primary">USER</span></td> <td v-if="!user.admin"><span class="badge badge-primary">USER</span></td>
<td>{{toLocal(user.updated_at)}}</td> <td class="d-none d-md-table-cell">{{toLocal(user.updated_at)}}</td>
<td class="text-right"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
<a @click.prevent="editUser(user, edit)" href="" class="btn btn-outline-secondary"><font-awesome-icon icon="user" /> Edit</a> <a @click.prevent="editUser(user, edit)" href="" class="btn btn-outline-secondary"><font-awesome-icon icon="user" /> Edit</a>

View File

@ -35,7 +35,7 @@ export default {
methods: { methods: {
async lastDaysFailures() { async lastDaysFailures() {
const start = this.nowSubtract(86400 * 30) const start = this.nowSubtract(86400 * 30)
this.failureData = await Api.service_failures_data(this.service.id, this.toUnix(start), this.toUnix(this.now()), "day") this.failureData = await Api.service_failures_data(this.service.id, this.toUnix(start), this.toUnix(this.now()), "24h")
} }
} }
} }

View File

@ -126,7 +126,7 @@
visible: function(newVal, oldVal) { visible: function(newVal, oldVal) {
if (newVal && !this.showing) { if (newVal && !this.showing) {
this.showing = true this.showing = true
this.chartHits("hour") this.chartHits("1h")
} }
} }
}, },
@ -135,8 +135,8 @@
const start = this.nowSubtract((3600 * 24) * 30) const start = this.nowSubtract((3600 * 24) * 30)
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), group) this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), group)
if (this.data.length === 0 && group !== "hour") { if (this.data.length === 0 && group !== "1h") {
await this.chartHits("hour") await this.chartHits("30m")
} }
this.series = [{ this.series = [{
name: this.service.name, name: this.service.name,

View File

@ -64,7 +64,7 @@
methods: { methods: {
async chartHeatmap() { async chartHeatmap() {
const start = this.nowSubtract((3600 * 24) * 7) const start = this.nowSubtract((3600 * 24) * 7)
const data = await Api.service_heatmap(this.service.id, this.toUnix(start), this.toUnix(new Date()), "hour") const data = await Api.service_heatmap(this.service.id, this.toUnix(start), this.toUnix(new Date()), "1h")
let dataArr = [] let dataArr = []
data.forEach(function(d) { data.forEach(function(d) {

View File

@ -49,9 +49,9 @@
} }
}, },
async mounted() { async mounted() {
this.set1 = await this.getHits(24, "hour") this.set1 = await this.getHits(24, "1h")
this.set1_name = this.calc(this.set1) this.set1_name = this.calc(this.set1)
this.set2 = await this.getHits(24 * 7, "day") this.set2 = await this.getHits(24 * 7, "24h")
this.set2_name = this.calc(this.set2) this.set2_name = this.calc(this.set2)
this.loaded = true this.loaded = true
}, },

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="col-12"> <div class="col-12">
<h1 class="text-black-50 mt-5"> <h3 class="text-black-50 mt-5">
{{message.id ? `Update ${message.title}` : "Create Message"}} {{message.id ? `Update ${message.title}` : "Create Message"}}
<button @click="removeEdit" v-if="message.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button> <button @click="removeEdit" v-if="message.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button>
</h1> </h3>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form @submit="saveMessage"> <form @submit="saveMessage">

View File

@ -1,9 +1,9 @@
<template> <template>
<div> <div>
<h1 class="text-black-50 mt-5"> <h3 class="text-black-50 mt-5">
{{user.id ? `Update ${user.username}` : "Create User"}} {{user.id ? `Update ${user.username}` : "Create User"}}
<button @click.prevent="removeEdit" v-if="user.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button></h1> <button @click.prevent="removeEdit" v-if="user.id" class="mt-3 btn float-right btn-danger btn-sm">Close</button></h3>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="container col-md-7 col-sm-12 mt-4 sm-container"> <div class="container col-md-7 col-sm-12 mt-4 sm-container index_container">
<Header/> <Header/>

View File

@ -250,7 +250,7 @@ export default {
const start = this.nowSubtract((3600 * 24) * 7) const start = this.nowSubtract((3600 * 24) * 7)
this.start_time = start this.start_time = start
this.end_time = new Date() this.end_time = new Date()
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), "hour") this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), "1h")
this.series = [{ this.series = [{
name: this.service.name, name: this.service.name,
...this.data ...this.data

View File

@ -187,7 +187,7 @@ func IsAdmin(r *http.Request) bool {
if !core.App.Setup { if !core.App.Setup {
return false return false
} }
if os.Getenv("GO_ENV") == "test" { if utils.Getenv("GO_ENV", false).(bool) {
return true return true
} }
claim, err := getJwtToken(r) claim, err := getJwtToken(r)

View File

@ -128,7 +128,7 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
groupQuery := database.ParseQueries(r, service.AllHits()) groupQuery := database.ParseQueries(r, service.AllHits())
objs, err := groupQuery.GraphData(database.ByAverage("latency")) objs, err := groupQuery.GraphData(database.ByAverage("latency", 1000))
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -164,7 +164,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
groupQuery := database.ParseQueries(r, service.AllHits()) groupQuery := database.ParseQueries(r, service.AllHits())
objs, err := groupQuery.GraphData(database.ByAverage("ping_time")) objs, err := groupQuery.GraphData(database.ByAverage("ping_time", 1000))
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return

View File

@ -90,6 +90,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
sendErrorJson(err, w, r)
return return
} }

View File

@ -104,7 +104,7 @@ func InitialSetup(configs *DbConfig) error {
admin := &users.User{ admin := &users.User{
Username: username, Username: username,
Password: utils.HashPassword(password), Password: password,
Email: "info@admin.com", Email: "info@admin.com",
Admin: null.NewNullBool(true), Admin: null.NewNullBool(true),
} }

View File

@ -14,6 +14,10 @@ import (
"github.com/hunterlong/statping/types/notifications" "github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services" "github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/types/users" "github.com/hunterlong/statping/types/users"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
) )
// InsertNotifierDB inject the Statping database instance to the Notifier package // InsertNotifierDB inject the Statping database instance to the Notifier package

View File

@ -6,6 +6,10 @@ import (
"github.com/prometheus/common/log" "github.com/prometheus/common/log"
"sync" "sync"
"time" "time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
) )
func Samples() { func Samples() {

View File

@ -18,6 +18,18 @@ func (h Hitters) Db() database.Database {
return h.db return h.db
} }
func (h Hitters) First() *Hit {
var hit Hit
h.db.Order("id ASC").Limit(1).Find(&hit)
return &hit
}
func (h Hitters) Last() *Hit {
var hit Hit
h.db.Order("id DESC").Limit(1).Find(&hit)
return &hit
}
func (h Hitters) Since(t time.Time) []*Hit { func (h Hitters) Since(t time.Time) []*Hit {
var hits []*Hit var hits []*Hit
h.db.Since(t).Find(&hits) h.db.Since(t).Find(&hits)
@ -30,9 +42,9 @@ func (h Hitters) List() []*Hit {
return hits return hits
} }
func (h Hitters) Last(amount int) []*Hit { func (h Hitters) LastCount(amounts int) []*Hit {
var hits []*Hit var hits []*Hit
h.db.Limit(amount).Find(&hits) h.db.Order("id asc").Limit(amounts).Find(&hits)
return hits return hits
} }

View File

@ -1,10 +1,16 @@
package hits package hits
import ( import (
"fmt"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"sync" "sync"
"time" "time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
) )
var SampleHits = 9900. var SampleHits = 9900.
@ -13,29 +19,38 @@ func Samples() {
tx := DB().Begin() tx := DB().Begin()
sg := new(sync.WaitGroup) sg := new(sync.WaitGroup)
createdAt := time.Now().Add(-1 * types.Month)
for i := int64(1); i <= 4; i++ { for i := int64(1); i <= 4; i++ {
sg.Add(1) err := createHitsAt(tx, i, sg)
if err != nil {
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano()) log.Error(err)
}
go func() { tx = DB().Begin()
defer sg.Done()
for hi := 0.; hi <= SampleHits; hi++ {
latency := p.Noise1D(hi / 500)
createdAt = createdAt.Add(1 * time.Minute)
hit := &Hit{
Service: i,
CreatedAt: createdAt.UTC(),
Latency: latency,
}
tx = tx.Create(&hit)
}
}()
} }
sg.Wait()
if err := tx.Commit().Error(); err != nil { }
log.Error(err)
} func createHitsAt(db database.Database, serviceID int64, sg *sync.WaitGroup) error {
createdAt := utils.Now().Add(-30 * types.Day)
p := utils.NewPerlin(2, 2, 5, utils.Now().UnixNano())
i := 0
for hi := 0.; hi <= SampleHits; hi++ {
latency := p.Noise1D(hi / 500)
createdAt = createdAt.Add(10 * time.Minute)
hit := &Hit{
Service: serviceID,
Latency: latency,
PingTime: latency * 0.15,
CreatedAt: createdAt,
}
db = db.Create(&hit)
fmt.Printf("Creating hit %d hit %d: %.2f %v\n", serviceID, hit.Id, latency, createdAt.String())
i++
}
return db.Commit().Error()
} }

View File

@ -10,16 +10,16 @@ func (i *Incident) Updates() []*IncidentUpdate {
} }
func (i *IncidentUpdate) Create() error { func (i *IncidentUpdate) Create() error {
db := database.DB().Create(&i) db := database.DB().Create(i)
return db.Error() return db.Error()
} }
func (i *IncidentUpdate) Update() error { func (i *IncidentUpdate) Update() error {
db := database.DB().Update(&i) db := database.DB().Update(i)
return db.Error() return db.Error()
} }
func (i *IncidentUpdate) Delete() error { func (i *IncidentUpdate) Delete() error {
db := database.DB().Delete(&i) db := database.DB().Delete(i)
return db.Error() return db.Error()
} }

View File

@ -1 +0,0 @@
package types

View File

@ -19,16 +19,16 @@ func All() []*Message {
} }
func (m *Message) Create() error { func (m *Message) Create() error {
db := DB().Create(&m) db := DB().Create(m)
return db.Error() return db.Error()
} }
func (m *Message) Update() error { func (m *Message) Update() error {
db := DB().Update(&m) db := DB().Update(m)
return db.Error() return db.Error()
} }
func (m *Message) Delete() error { func (m *Message) Delete() error {
db := DB().Delete(&m) db := DB().Delete(m)
return db.Error() return db.Error()
} }

View File

@ -2,7 +2,6 @@ package services
import ( import (
"fmt" "fmt"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types/failures" "github.com/hunterlong/statping/types/failures"
"strings" "strings"
"time" "time"
@ -16,10 +15,10 @@ func (s *Service) AllFailures() failures.Failurer {
return failures.AllFailures(s) return failures.AllFailures(s)
} }
func (s *Service) LastFailures(amount int) []*failures.Failure { func (s *Service) LastFailure() *failures.Failure {
var fail []*failures.Failure var fail failures.Failure
database.DB().Limit(amount).Find(&fail) failures.DB().Where("service = ?", s.Id).Order("id desc").Limit(1).Find(&fail)
return fail return &fail
} }
func (s *Service) FailuresCount() int { func (s *Service) FailuresCount() int {
@ -35,11 +34,11 @@ func (s *Service) FailuresSince(t time.Time) []*failures.Failure {
} }
func (s *Service) DowntimeText() string { func (s *Service) DowntimeText() string {
last := s.LastFailures(1) last := s.LastFailure()
if len(last) == 0 { if last == nil {
return "" return ""
} }
return parseError(last[0]) return parseError(last)
} }
// ParseError returns a human readable error for a Failure // ParseError returns a human readable error for a Failure

View File

@ -9,6 +9,14 @@ func (s *Service) HitsColumnID() (string, int64) {
return "service", s.Id return "service", s.Id
} }
func (s *Service) FirstHit() *hits.Hit {
return hits.AllHits(s).First()
}
func (s *Service) LastHit() *hits.Hit {
return hits.AllHits(s).Last()
}
func (s *Service) AllHits() hits.Hitters { func (s *Service) AllHits() hits.Hitters {
return hits.AllHits(s) return hits.AllHits(s)
} }

View File

@ -186,14 +186,14 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
// Downtime returns the amount of time of a offline service // Downtime returns the amount of time of a offline service
func (s *Service) Downtime() time.Duration { func (s *Service) Downtime() time.Duration {
hits := s.AllHits().Last(1) hit := s.LastHit()
fail := s.AllFailures().Last(1) fail := s.LastFailure()
if len(fail) == 0 { if hit == nil {
return time.Duration(0) return time.Duration(0)
} }
if len(fail) == 0 { if fail == nil {
return time.Now().UTC().Sub(fail[0].CreatedAt.UTC()) return utils.Now().Sub(fail.CreatedAt)
} }
since := fail[0].CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
return since return fail.CreatedAt.Sub(hit.CreatedAt)
} }

View File

@ -15,11 +15,6 @@ func AuthUser(username, password string) (*User, bool) {
log.Warnln(fmt.Errorf("user %v not found", username)) log.Warnln(fmt.Errorf("user %v not found", username))
return nil, false return nil, false
} }
fmt.Println(username, password)
fmt.Println(username, user.Password)
if CheckHash(password, user.Password) { if CheckHash(password, user.Password) {
user.UpdatedAt = time.Now().UTC() user.UpdatedAt = time.Now().UTC()
user.Update() user.Update()

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"github.com/hunterlong/statping/database" "github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"github.com/prometheus/common/log"
"time" "time"
) )
@ -13,14 +14,14 @@ func DB() database.Database {
func Find(id int64) (*User, error) { func Find(id int64) (*User, error) {
var user User var user User
db := DB().Where("id = ?", id).Find(user) db := DB().Where("id = ?", id).Find(&user)
return &user, db.Error() return &user, db.Error()
} }
func FindByUsername(username string) (*User, error) { func FindByUsername(username string) (*User, error) {
var user *User var user User
db := DB().Where("username = ?", username).Find(user) db := DB().Where("username = ?", username).Find(&user)
return user, db.Error() return &user, db.Error()
} }
func All() []*User { func All() []*User {
@ -35,21 +36,27 @@ func (u *User) Create() error {
return errors.New("did not supply user password") return errors.New("did not supply user password")
} }
u.Password = utils.HashPassword(u.Password) u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5) u.ApiKey = utils.NewSHA1Hash(16)
u.ApiSecret = utils.NewSHA1Hash(10) u.ApiSecret = utils.NewSHA1Hash(16)
db := DB().Create(u) db := DB().Create(u)
if db.Error() == nil {
log.Warnf("User #%d (%s) has been created", u.Id, u.Username)
}
return db.Error() return db.Error()
} }
func (u *User) Update() error { func (u *User) Update() error {
u.ApiKey = utils.NewSHA1Hash(5) //u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10) //u.ApiSecret = utils.NewSHA1Hash(10)
db := DB().Update(&u) db := DB().Update(u)
return db.Error() return db.Error()
} }
func (u *User) Delete() error { func (u *User) Delete() error {
db := DB().Delete(u) db := DB().Delete(u)
if db.Error() == nil {
log.Warnf("User #%d (%s) has been deleted")
}
return db.Error() return db.Error()
} }