mirror of https://github.com/statping/statping
db changes
parent
66ec613953
commit
7dc285295b
6
Makefile
6
Makefile
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package types
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue