mirror of https://github.com/statping/statping
vue
parent
b0ea8b3621
commit
bbf8073315
|
@ -131,10 +131,6 @@ func main() {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = confgs.VerifyMigration(); err != nil {
|
|
||||||
exit(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exists := confgs.Db.HasTable("core")
|
exists := confgs.Db.HasTable("core")
|
||||||
if !exists {
|
if !exists {
|
||||||
var srvs int64
|
var srvs int64
|
||||||
|
@ -161,6 +157,10 @@ func main() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = confgs.DatabaseChanges(); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := confgs.MigrateDatabase(); err != nil {
|
if err := confgs.MigrateDatabase(); err != nil {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/statping/statping/types"
|
"github.com/statping/statping/types"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
|
@ -144,7 +145,7 @@ type isObject interface {
|
||||||
Db() Database
|
Db() Database
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseQueries(r *http.Request, o isObject) *GroupQuery {
|
func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
||||||
fields := parseGet(r)
|
fields := parseGet(r)
|
||||||
grouping := fields.Get("group")
|
grouping := fields.Get("group")
|
||||||
startField := utils.ToInt(fields.Get("start"))
|
startField := utils.ToInt(fields.Get("start"))
|
||||||
|
@ -179,11 +180,15 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
|
||||||
db: q,
|
db: q,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Start.After(query.End) {
|
||||||
|
return nil, errors.New("start time is after ending time")
|
||||||
|
}
|
||||||
|
|
||||||
if startField == 0 {
|
if startField == 0 {
|
||||||
query.Start = time.Now().Add(-7 * types.Day).UTC()
|
query.Start = utils.Now().Add(-7 * types.Day)
|
||||||
}
|
}
|
||||||
if endField == 0 {
|
if endField == 0 {
|
||||||
query.End = time.Now().UTC()
|
query.End = utils.Now()
|
||||||
}
|
}
|
||||||
if query.End.After(utils.Now()) {
|
if query.End.After(utils.Now()) {
|
||||||
query.End = utils.Now()
|
query.End = utils.Now()
|
||||||
|
@ -203,7 +208,7 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
|
||||||
}
|
}
|
||||||
query.db = q
|
query.db = q
|
||||||
|
|
||||||
return query
|
return query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseForm(r *http.Request) url.Values {
|
func parseForm(r *http.Request) url.Values {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<router-view :loaded="loaded"/>
|
<router-view :app="app" :loaded="loaded"/>
|
||||||
<Footer :logged_in="logged_in" :version="version" v-if="$route.path !== '/setup'"/>
|
<Footer :logged_in="logged_in" :version="version" v-if="$route.path !== '/setup'"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -19,10 +19,13 @@
|
||||||
loaded: false,
|
loaded: false,
|
||||||
version: "",
|
version: "",
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
app: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
await this.$store.dispatch('loadRequired')
|
this.app = await this.$store.dispatch('loadRequired')
|
||||||
|
|
||||||
|
this.app = {...this.$store.state}
|
||||||
|
|
||||||
if (this.$store.getters.core.logged_in) {
|
if (this.$store.getters.core.logged_in) {
|
||||||
await this.$store.dispatch('loadAdmin')
|
await this.$store.dispatch('loadAdmin')
|
||||||
|
|
|
@ -6,7 +6,34 @@ HTML,BODY {
|
||||||
}
|
}
|
||||||
|
|
||||||
.index-chart {
|
.index-chart {
|
||||||
height: 490px;
|
height: $service-card-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-service-card {
|
||||||
|
border: 1px solid #dcdcdc87;
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
height: 155px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-service {
|
||||||
|
height: 89vh;
|
||||||
|
/*Animation*/
|
||||||
|
-webkit-transition: height 2s ease;
|
||||||
|
-moz-transition: height 2s ease;
|
||||||
|
-o-transition: height 2s ease;
|
||||||
|
-ms-transition: height 2s ease;
|
||||||
|
transition: height 2s ease;
|
||||||
|
|
||||||
|
.stats_area {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-chart {
|
||||||
|
bottom: -15px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chartmarker {
|
.chartmarker {
|
||||||
|
@ -167,7 +194,19 @@ HTML,BODY {
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-5 {
|
.font-5 {
|
||||||
font-size: 20pt;
|
font-size: 17pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-6 {
|
||||||
|
font-size: 24pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-7 {
|
||||||
|
font-size: 31pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-8 {
|
||||||
|
font-size: 38pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
|
@ -225,6 +264,7 @@ HTML,BODY {
|
||||||
|
|
||||||
.card-body H4 A {
|
.card-body H4 A {
|
||||||
color: $service-title;
|
color: $service-title;
|
||||||
|
font-size: $service-title-size;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +288,7 @@ HTML,BODY {
|
||||||
|
|
||||||
.service-chart-heatmap {
|
.service-chart-heatmap {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 300px;
|
height: 180px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.index-chart {
|
.index-chart {
|
||||||
height: 33vh;
|
height: 380px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sm-container {
|
.sm-container {
|
||||||
|
|
|
@ -8,9 +8,11 @@ $description-color: #939393;
|
||||||
$service-background: #ffffff;
|
$service-background: #ffffff;
|
||||||
$service-border: 1px solid rgba(0,0,0,.125);
|
$service-border: 1px solid rgba(0,0,0,.125);
|
||||||
$service-title: #444444;
|
$service-title: #444444;
|
||||||
|
$service-title-size: 1.8rem;
|
||||||
$service-stats-color: #4f4f4f;
|
$service-stats-color: #4f4f4f;
|
||||||
$service-description-color: #fff;
|
$service-description-color: #fff;
|
||||||
$service-stats-size: 2.3rem;
|
$service-stats-size: 2.3rem;
|
||||||
|
$service-card-height: 490px;
|
||||||
|
|
||||||
/* Button Colors */
|
/* Button Colors */
|
||||||
$success-color: #47d337;
|
$success-color: #47d337;
|
||||||
|
@ -25,7 +27,6 @@ $footer-display: block;
|
||||||
/* Global Settings */
|
/* Global Settings */
|
||||||
$global-border-radius: 0.2rem;
|
$global-border-radius: 0.2rem;
|
||||||
|
|
||||||
|
|
||||||
/* Mobile Settings */
|
/* Mobile Settings */
|
||||||
$sm-background-color: #fcfcfc;
|
$sm-background-color: #fcfcfc;
|
||||||
$sm-border-radius: 0rem;
|
$sm-border-radius: 0rem;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<a v-for="(service, index) in $store.getters.servicesInGroup(group.id)" v-bind:key="index" class="service_li list-group-item list-group-item-action">
|
<a v-for="(service, index) in $store.getters.servicesInGroup(group.id)" v-bind:key="index" class="service_li list-group-item list-group-item-action">
|
||||||
<router-link class="no-decoration" :to="serviceLink(service)">{{service.name}}</router-link>
|
<router-link class="no-decoration" :to="serviceLink(service)">{{service.name}}</router-link>
|
||||||
<span class="badge bg-success float-right">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
||||||
|
|
||||||
<GroupServiceFailures :service="service"/>
|
<GroupServiceFailures :service="service"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="col-12 text-center mb-4 mt-sm-3 header-title">{{$store.getters.core.name}}</h1>
|
<h1 class="col-12 text-center pt-4 mt-4 header-title">{{$store.getters.core.name}}</h1>
|
||||||
<h5 class="col-12 text-center mb-5 header-desc">{{$store.getters.core.description}}</h5>
|
<h5 class="col-12 text-center mb-5 header-desc">{{$store.getters.core.description}}</h5>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="alert alert-primary" role="alert">
|
<div class="alert alert-primary pb-4 pt-3 mt-5 mb-5" role="alert">
|
||||||
<h3>{{message.title}}</h3>
|
<h3 class="mb-3">{{message.title}}</h3>
|
||||||
<span class="mb-3">{{message.description}}</span>
|
<span class="mb-3">{{message.description}}</span>
|
||||||
<div class="d-block mt-2 mb-4">
|
<div class="row d-block mt-3">
|
||||||
<span class="float-left small">
|
<span class="col-12 col-md-6 text-left small">
|
||||||
Started {{toLocal(message.start_on)}} ({{duration(new Date(), message.start_on)}} ago)
|
Started {{niceDate(message.start_on)}} ({{ago(parseISO(message.start_on))}} ago)
|
||||||
</span>
|
</span>
|
||||||
<span class="float-right small">
|
<span class="col-12 col-md-6 text-right float-right small">
|
||||||
Ends on {{toLocal(message.end_on)}} (in {{duration(message.end_on, new Date())}})</span>
|
Ends on {{niceDate(message.end_on)}} (in {{ago(parseISO(message.end_on))}})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<div class="col-6 mt-4">
|
||||||
|
<div class="col-12 sub-service-card">
|
||||||
|
<div class="col-8 float-left p-0 mt-1 mb-3">
|
||||||
|
<span class="font-5 d-block">{{title}}</span>
|
||||||
|
<span class="text-muted font-3 d-block font-weight-bold">{{subtitle}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 float-right text-right mt-2 p-0">
|
||||||
|
<span class="text-success font-5 font-weight-bold">{{value}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MiniSparkLine :series="[{name: 'okokokok', data:[{x: '2019-01-01', y: 120},{x: '2019-01-02', y: 160},{x: '2019-01-03', y: 240},{x: '2019-01-04', y: 45}]}]"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Api from "../../API";
|
||||||
|
import MiniSparkLine from './MiniSparkLine';
|
||||||
|
import ServiceSparkLine from './ServiceSparkLine';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Analytics',
|
||||||
|
components: { MiniSparkLine, ServiceSparkLine },
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.latencyYesterday();
|
||||||
|
},
|
||||||
|
async latencyYesterday() {
|
||||||
|
const todayTime = await Api.service_hits(this.service.id, this.toUnix(this.nowSubtract(86400)), this.toUnix(new Date()), this.group, false)
|
||||||
|
const fetched = await Api.service_hits(this.service.id, this.start, this.end, this.group, false)
|
||||||
|
|
||||||
|
let todayAmount = this.addAmounts(todayTime)
|
||||||
|
let yesterday = this.addAmounts(fetched)
|
||||||
|
|
||||||
|
window.console.log(todayAmount)
|
||||||
|
window.console.log(yesterday)
|
||||||
|
|
||||||
|
},
|
||||||
|
addAmounts(data) {
|
||||||
|
let total = 0
|
||||||
|
data.forEach((f) => {
|
||||||
|
total += parseInt(f.amount)
|
||||||
|
});
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<template v-if="series.length">
|
||||||
|
<apexchart width="100%" height="70" type="bar" :options="chartOpts" :series="series"></apexchart>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MiniSparkLine',
|
||||||
|
props: {
|
||||||
|
series: {
|
||||||
|
type: Array,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
title () {
|
||||||
|
|
||||||
|
},
|
||||||
|
subtitle () {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartOpts: {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 180,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight'
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
colors: ['#b3bdc3'],
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: false,
|
||||||
|
x: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
formatter: (value) => { return value + "%" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
fontSize: '28px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
text: this.subtitle,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
fontSize: '14px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -1,26 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mb-4">
|
<div class="mb-md-4 mb-5">
|
||||||
<div class="card index-chart">
|
<div class="card index-chart" :class="{'expanded-service': expanded}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h4 class="mt-3">
|
<h4 class="mt-3">
|
||||||
<router-link :to="serviceLink(service)" :in_service="service">{{service.name}}</router-link>
|
<router-link :to="serviceLink(service)" class="d-inline-block text-truncate" style="max-width: 65vw;" :in_service="service">{{service.name}}</router-link>
|
||||||
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online}">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online}">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<ServiceTopStats :service="service"/>
|
<ServiceTopStats :service="service"/>
|
||||||
|
|
||||||
|
<div v-if="expanded" class="row">
|
||||||
|
<Analytics title="Last Failure" level="100" value="35%" subtitle="417 Days ago"/>
|
||||||
|
<Analytics title="Total Failures" level="100" value="35%" subtitle="417 Days ago"/>
|
||||||
|
<Analytics title="Highest Latency" level="100" value="450ms" subtitle="417 Days ago"/>
|
||||||
|
<Analytics title="Lowest Latency" level="100" value="120ms" subtitle="417 Days ago"/>
|
||||||
|
<Analytics title="Total Uptime" level="100" value="35%" subtitle="850ms"/>
|
||||||
|
<Analytics title="Total Downtime" level="100" value="35%" subtitle="32ms"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-observe-visibility="visibleChart" class="chart-container">
|
<div v-if="!expanded" v-observe-visibility="visibleChart" class="chart-container">
|
||||||
<ServiceChart :service="service" :visible="visible"/>
|
<ServiceChart :service="service" :visible="visible"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row lower_canvas full-col-12 text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
<div class="row lower_canvas full-col-12 text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||||
<div class="col-md-8 col-6">
|
<div class="col-md-8 col-6">
|
||||||
<div class="dropup" :class="{show: dropDownMenu}">
|
<div class="dropup" :class="{show: dropDownMenu}">
|
||||||
<button style="font-size: 10pt;" @focusout="dropDownMenu = false" @click="dropDownMenu = !dropDownMenu" type="button" class="col-4 float-left btn btn-sm float-right btn-block text-white dropdown-toggle service_scale" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button style="font-size: 10pt;" @focusout="dropDownMenu = false" @click="dropDownMenu = !dropDownMenu" type="button" class="d-none col-4 float-left btn btn-sm float-right btn-block text-white dropdown-toggle service_scale" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
24 Hours
|
24 Hours
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" :class="{show: dropDownMenu}">
|
<div class="dropdown-menu" :class="{show: dropDownMenu}">
|
||||||
|
@ -34,8 +43,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 col-6 float-right">
|
<div class="col-md-4 col-6 float-right">
|
||||||
<router-link :to="serviceLink(service)" class="btn btn-sm float-right dyn-dark btn-block text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
<router-link :to="serviceLink(service)" class="d-none btn btn-sm float-right dyn-dark btn-block text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||||
View Service</router-link>
|
View Service</router-link>
|
||||||
|
<button @click="expanded = !expanded" class="btn btn-sm float-right dyn-dark btn-block text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">View Service</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="expanded" class="row">
|
||||||
|
<Analytics title="Last Failure" value="417 Days ago"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -44,12 +58,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Analytics from './Analytics';
|
||||||
import ServiceChart from "./ServiceChart";
|
import ServiceChart from "./ServiceChart";
|
||||||
import ServiceTopStats from "@/components/Service/ServiceTopStats";
|
import ServiceTopStats from "@/components/Service/ServiceTopStats";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ServiceBlock',
|
name: 'ServiceBlock',
|
||||||
components: {ServiceTopStats, ServiceChart},
|
components: { Analytics, ServiceTopStats, ServiceChart},
|
||||||
props: {
|
props: {
|
||||||
service: {
|
service: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -58,6 +73,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
expanded: false,
|
||||||
visible: false,
|
visible: false,
|
||||||
dropDownMenu: false,
|
dropDownMenu: false,
|
||||||
timeframes: [
|
timeframes: [
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template v-show="showing">
|
<template v-show="showing">
|
||||||
<apexchart v-if="ready" width="100%" height="235" type="area" :options="chartOptions" :series="series"/>
|
<apexchart v-if="ready" class="service-chart" width="100%" height="100%" type="area" :options="chartOptions" :series="series"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
text: 'Loading...'
|
text: 'Loading...'
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
height: 210,
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
type: "area",
|
type: "area",
|
||||||
animations: {
|
animations: {
|
||||||
|
@ -163,14 +163,14 @@
|
||||||
visible: function(newVal, oldVal) {
|
visible: function(newVal, oldVal) {
|
||||||
if (newVal && !this.showing) {
|
if (newVal && !this.showing) {
|
||||||
this.showing = true
|
this.showing = true
|
||||||
this.chartHits("2h")
|
this.chartHits("1h")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async chartHits(group) {
|
async chartHits(group) {
|
||||||
window.console.log(this.service.created_at)
|
const start = this.nowSubtract(84600 * 3)
|
||||||
this.data = await Api.service_hits(this.service.id, this.toUnix(this.service.created_at), this.toUnix(new Date()), group, false)
|
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), group, false)
|
||||||
|
|
||||||
if (this.data.length === 0 && group !== "1h") {
|
if (this.data.length === 0 && group !== "1h") {
|
||||||
await this.chartHits("1h")
|
await this.chartHits("1h")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<apexchart v-if="ready" width="100%" height="300" type="heatmap" :options="chartOptions" :series="series"></apexchart>
|
<apexchart v-if="ready" width="100%" height="180" type="heatmap" :options="plotOptions" :series="series"></apexchart>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -20,19 +20,32 @@
|
||||||
return {
|
return {
|
||||||
ready: false,
|
ready: false,
|
||||||
data: [],
|
data: [],
|
||||||
chartOptions: {
|
plotOptions: {
|
||||||
heatmap: {
|
chart: {
|
||||||
|
selection: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: [ "#cb3d36" ],
|
||||||
|
enableShades: true,
|
||||||
|
shadeIntensity: 0.5,
|
||||||
colorScale: {
|
colorScale: {
|
||||||
ranges: [{
|
ranges: [ {
|
||||||
from: 0,
|
from: 0,
|
||||||
to: 1,
|
to: 0,
|
||||||
color: 'rgba(235,63,48,0.69)',
|
color: '#bababa',
|
||||||
name: 'low',
|
name: 'none',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: 2,
|
from: 2,
|
||||||
to: 10,
|
to: 10,
|
||||||
color: 'rgba(245,43,43,0.58)',
|
color: '#cb3d36',
|
||||||
name: 'medium',
|
name: 'medium',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -42,76 +55,66 @@
|
||||||
name: 'high',
|
name: 'high',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
},
|
},
|
||||||
chart: {
|
xaxis: {
|
||||||
height: "100%",
|
tickAmount: '1',
|
||||||
width: "100%",
|
tickPlacement: 'between',
|
||||||
type: 'heatmap',
|
min: 1,
|
||||||
toolbar: {
|
max: 31,
|
||||||
show: false
|
type: "numeric",
|
||||||
}
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
enableShades: true,
|
|
||||||
shadeIntensity: 0.5,
|
|
||||||
colors: ["#d53a3b"],
|
|
||||||
series: [{data: [{}]}],
|
|
||||||
yaxis: {
|
|
||||||
labels: {
|
labels: {
|
||||||
formatter: (value) => {
|
show: true
|
||||||
return value
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: true,
|
enabled: false
|
||||||
x: {
|
}
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
formatter: function(val, opts) { return val+" Failures" },
|
|
||||||
title: {
|
|
||||||
formatter: (seriesName) => seriesName,
|
|
||||||
},
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
show: true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: [{
|
series: [ {
|
||||||
data: []
|
data: []
|
||||||
}]
|
} ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async chartHeatmap() {
|
async chartHeatmap() {
|
||||||
let start = new Date(new Date().getUTCFullYear(), new Date().getUTCMonth()-3, 1);
|
let start = new Date(new Date().getUTCFullYear(), new Date().getUTCMonth()-2, 1);
|
||||||
|
|
||||||
let monthData = [];
|
let monthData = [];
|
||||||
|
let monthNum = start.getUTCMonth()
|
||||||
|
|
||||||
for (i=0; i<=3; i++) {
|
for (let i=1; i<=3; i++) {
|
||||||
let end = this.lastDayOfMonth(start.getUTCMonth()+start)
|
let end = this.lastDayOfMonth(monthNum)
|
||||||
const inputdata = this.heatmapData(start,end)
|
|
||||||
|
window.console.log("getting: ",start, end)
|
||||||
|
|
||||||
|
const inputdata = await this.heatmapData(start, end)
|
||||||
monthData.push(inputdata)
|
monthData.push(inputdata)
|
||||||
|
start = new Date(start.getUTCFullYear(), start.getUTCMonth()+1, 1);
|
||||||
|
monthNum += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
this.series = monthData
|
window.console.log(monthData)
|
||||||
|
this.series = monthData.reverse()
|
||||||
this.ready = true
|
this.ready = true
|
||||||
},
|
},
|
||||||
async heatmapData(start, end) {
|
async heatmapData(start, end) {
|
||||||
console.log(start, end)
|
|
||||||
|
window.console.log("start: ", start)
|
||||||
|
window.console.log("end: ", end)
|
||||||
|
|
||||||
const data = await Api.service_failures_data(this.service.id, this.toUnix(start), this.toUnix(end), "24h", true)
|
const data = await Api.service_failures_data(this.service.id, this.toUnix(start), this.toUnix(end), "24h", true)
|
||||||
|
|
||||||
let dataArr = []
|
let dataArr = []
|
||||||
data.forEach(function(d) {
|
data.forEach((d) => {
|
||||||
dataArr.push({x: d.timeframe, y: d.amount});
|
dataArr.push({x: this.parseTime(d.timeframe).getUTCDate(), y: d.amount});
|
||||||
});
|
});
|
||||||
|
|
||||||
let date = new Date(dataArr[0].x);
|
let date = new Date(dataArr[0].x);
|
||||||
const output = [{name: date.toLocaleString('en-us', { month: 'long'}), data: dataArr}]
|
return {name: start.toLocaleString('en-us', { month: 'long'}), data: dataArr}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
const { zonedTimeToUtc, utcToZonedTime, lastDayOfMonth, subSeconds, parse, parseISO, getUnixTime, fromUnixTime, format, differenceInSeconds, formatDistanceToNow, formatDistance } = require('date-fns')
|
const { zonedTimeToUtc, utcToZonedTime, lastDayOfMonth, subSeconds, parse, getUnixTime, fromUnixTime, differenceInSeconds, formatDistance } = require('date-fns')
|
||||||
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
|
||||||
|
import format from 'date-fns/format'
|
||||||
|
import parseISO from 'date-fns/parseISO'
|
||||||
|
|
||||||
export default Vue.mixin({
|
export default Vue.mixin({
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -7,10 +10,10 @@ export default Vue.mixin({
|
||||||
return new Date()
|
return new Date()
|
||||||
},
|
},
|
||||||
current() {
|
current() {
|
||||||
return parse(new Date())
|
return parseISO(new Date())
|
||||||
},
|
},
|
||||||
utc(val) {
|
utc(val) {
|
||||||
return fromUnixTime(this.toUnix(val) + val.getTimezoneOffset() * 60 * 1000)
|
return new Date.UTC(val)
|
||||||
},
|
},
|
||||||
ago(t1) {
|
ago(t1) {
|
||||||
return formatDistanceToNow(t1)
|
return formatDistanceToNow(t1)
|
||||||
|
@ -25,11 +28,14 @@ export default Vue.mixin({
|
||||||
return formatDistance(t1, t2)
|
return formatDistance(t1, t2)
|
||||||
},
|
},
|
||||||
niceDate(val) {
|
niceDate(val) {
|
||||||
return this.parseTime(val).format('LLLL')
|
return format(parseISO(val), "EEEE, MMM do h:mma")
|
||||||
},
|
},
|
||||||
parseTime(val) {
|
parseTime(val) {
|
||||||
return parseISO(val)
|
return parseISO(val)
|
||||||
},
|
},
|
||||||
|
parseISO(v) {
|
||||||
|
return parseISO(v)
|
||||||
|
},
|
||||||
toLocal(val, suf = 'at') {
|
toLocal(val, suf = 'at') {
|
||||||
const t = this.parseTime(val)
|
const t = this.parseTime(val)
|
||||||
return format(t, `EEEE, MMM do h:mma`)
|
return format(t, `EEEE, MMM do h:mma`)
|
||||||
|
@ -41,7 +47,7 @@ export default Vue.mixin({
|
||||||
return fromUnixTime(val)
|
return fromUnixTime(val)
|
||||||
},
|
},
|
||||||
isBetween(t1, t2) {
|
isBetween(t1, t2) {
|
||||||
return differenceInSeconds(parseISO(t1), parseISO(t2)) > 0
|
return differenceInSeconds(t1, t2) >= 0
|
||||||
},
|
},
|
||||||
hour() {
|
hour() {
|
||||||
return 3600
|
return 3600
|
||||||
|
@ -111,10 +117,10 @@ export default Vue.mixin({
|
||||||
return {data: newSet}
|
return {data: newSet}
|
||||||
},
|
},
|
||||||
lastDayOfMonth(month) {
|
lastDayOfMonth(month) {
|
||||||
return new Date(new Date().getUTCFullYear(), month + 1, 0);
|
return new Date(Date.UTC(new Date().getUTCFullYear(), month + 1, 0))
|
||||||
},
|
},
|
||||||
firstDayOfMonth(month) {
|
firstDayOfMonth(month) {
|
||||||
return new Date(new Date().getUTCFullYear(), month, 1).getUTCDate();
|
return new Date(Date.UTC(new Date().getUTCFullYear(), month, 1)).getUTCDate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container col-md-7 col-sm-12 mt-4 sm-container index_container pt-5">
|
<div class="container col-md-7 col-sm-12 sm-container index_container">
|
||||||
|
|
||||||
<Header/>
|
<Header/>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="ready" class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
<div v-if="service" class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||||
|
|
||||||
<div class="col-12 mb-4">
|
<div class="col-12 mb-4">
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<h4 class="mt-2">
|
<h4 class="mt-2">
|
||||||
<router-link to="/">{{$store.getters.core.name}}</router-link> - {{service.name}}
|
<router-link to="/" class="text-black-50 text-decoration-none">{{$store.getters.core.name}}</router-link> - <span class="text-muted">{{service.name}}</span>
|
||||||
<span class="badge float-right d-none d-md-block" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
<span class="badge float-right d-none d-md-block" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||||
{{service.online ? "ONLINE" : "OFFLINE"}}
|
{{service.online ? "ONLINE" : "OFFLINE"}}
|
||||||
</span>
|
</span>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
<ServiceTopStats :service="service"/>
|
<ServiceTopStats :service="service"/>
|
||||||
|
|
||||||
<div v-for="(message, index) in messages" v-if="messageInRange(message)">
|
<div v-for="(message, index) in $store.getters.serviceMessages(service.id)" v-if="messageInRange(message)">
|
||||||
<MessageBlock :message="message"/>
|
<MessageBlock :message="message"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -33,13 +33,13 @@
|
||||||
<apexchart width="100%" height="420" type="area" :options="chartOptions" :series="series"></apexchart>
|
<apexchart width="100%" height="420" type="area" :options="chartOptions" :series="series"></apexchart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="service-chart-heatmap mt-3 mb-4">
|
<div class="service-chart-heatmap mt-5 mb-4">
|
||||||
<ServiceHeatmap :service="service"/>
|
<ServiceHeatmap :service="service"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="series" class="service-chart-container">
|
<!-- <div v-if="series" class="service-chart-container">-->
|
||||||
<apexchart width="100%" height="300" type="range" :options="dailyRangeOpts" :series="series"></apexchart>
|
<!-- <apexchart width="100%" height="300" type="range" :options="dailyRangeOpts" :series="series"></apexchart>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<nav v-if="service.failures" class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs">
|
<nav v-if="service.failures" class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs">
|
||||||
<a @click="tab='failures'" class="flex-sm-fill text-sm-center nav-link active">Failures</a>
|
<a @click="tab='failures'" class="flex-sm-fill text-sm-center nav-link active">Failures</a>
|
||||||
|
@ -95,8 +95,10 @@
|
||||||
import Checkin from "../forms/Checkin";
|
import Checkin from "../forms/Checkin";
|
||||||
import ServiceHeatmap from "@/components/Service/ServiceHeatmap";
|
import ServiceHeatmap from "@/components/Service/ServiceHeatmap";
|
||||||
import ServiceTopStats from "@/components/Service/ServiceTopStats";
|
import ServiceTopStats from "@/components/Service/ServiceTopStats";
|
||||||
|
import store from '../store'
|
||||||
import flatPickr from 'vue-flatpickr-component';
|
import flatPickr from 'vue-flatpickr-component';
|
||||||
import 'flatpickr/dist/flatpickr.css';
|
import 'flatpickr/dist/flatpickr.css';
|
||||||
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
const axisOptions = {
|
const axisOptions = {
|
||||||
labels: {
|
labels: {
|
||||||
|
@ -134,16 +136,15 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: null,
|
id: this.$route.params.id,
|
||||||
tab: "failures",
|
tab: "failures",
|
||||||
service: {},
|
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
ready: false,
|
ready: true,
|
||||||
data: null,
|
data: null,
|
||||||
messages: [],
|
messages: [],
|
||||||
failures: [],
|
failures: [],
|
||||||
start_time: "",
|
start_time: this.nowSubtract(84600 * 30),
|
||||||
end_time: "",
|
end_time: new Date(),
|
||||||
dailyRangeOpts: {
|
dailyRangeOpts: {
|
||||||
chart: {
|
chart: {
|
||||||
height: 500,
|
height: 500,
|
||||||
|
@ -153,6 +154,19 @@ export default {
|
||||||
},
|
},
|
||||||
chartOptions: {
|
chartOptions: {
|
||||||
chart: {
|
chart: {
|
||||||
|
events: {
|
||||||
|
beforeZoom: async (chartContext, { xaxis }) => {
|
||||||
|
const start = (xaxis.min / 1000).toFixed(0)
|
||||||
|
const end = (xaxis.max / 1000).toFixed(0)
|
||||||
|
await this.chartHits(start, end, "10m")
|
||||||
|
return {
|
||||||
|
xaxis: {
|
||||||
|
min: this.fromUnix(start),
|
||||||
|
max: this.fromUnix(end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
height: 500,
|
height: 500,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
type: "area",
|
type: "area",
|
||||||
|
@ -163,39 +177,65 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
enabled: false
|
enabled: true
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
enabled: false
|
enabled: true
|
||||||
},
|
},
|
||||||
toolbar: {
|
toolbar: {
|
||||||
show: false
|
show: true
|
||||||
},
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
curve: 'smooth',
|
||||||
|
lineCap: 'butt',
|
||||||
},
|
},
|
||||||
grid: {
|
|
||||||
show: true,
|
|
||||||
padding: {
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
...axisOptions
|
labels: {
|
||||||
},
|
show: true
|
||||||
yaxis: {
|
|
||||||
...axisOptions
|
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false,
|
enabled: true
|
||||||
marker: {
|
}
|
||||||
show: false,
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: true,
|
||||||
|
custom: function ({ series, seriesIndex, dataPointIndex, w }) {
|
||||||
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
|
let val = series[seriesIndex][dataPointIndex];
|
||||||
|
if (val >= 1000) {
|
||||||
|
val = (val * 0.1).toFixed(0) + " milliseconds"
|
||||||
|
} else {
|
||||||
|
val = (val * 0.01).toFixed(0) + " microseconds"
|
||||||
|
}
|
||||||
|
return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
|
||||||
|
},
|
||||||
|
fixed: {
|
||||||
|
enabled: true,
|
||||||
|
position: 'topRight',
|
||||||
|
offsetX: -30,
|
||||||
|
offsetY: 40,
|
||||||
},
|
},
|
||||||
x: {
|
x: {
|
||||||
show: false,
|
show: false,
|
||||||
}
|
format: 'dd MMM',
|
||||||
|
formatter: undefined,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
formatter: undefined,
|
||||||
|
title: {
|
||||||
|
formatter: (seriesName) => seriesName,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: false,
|
show: false,
|
||||||
|
@ -205,7 +245,7 @@ export default {
|
||||||
},
|
},
|
||||||
floating: true,
|
floating: true,
|
||||||
axisTicks: {
|
axisTicks: {
|
||||||
show: false
|
show: true
|
||||||
},
|
},
|
||||||
axisBorder: {
|
axisBorder: {
|
||||||
show: false
|
show: false
|
||||||
|
@ -231,23 +271,32 @@ export default {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
computed: {
|
||||||
const id = this.$attrs.id
|
service () {
|
||||||
this.id = id
|
return this.$store.getters.serviceByAll(this.id)
|
||||||
let service;
|
|
||||||
if (this.isInt(id)) {
|
|
||||||
service = this.$store.getters.serviceById(id)
|
|
||||||
} else {
|
|
||||||
service = this.$store.getters.serviceByPermalink(id)
|
|
||||||
}
|
}
|
||||||
this.service = service
|
},
|
||||||
this.getService(service)
|
watch: {
|
||||||
this.messages = this.$store.getters.serviceMessages(service.id)
|
service: function(n, o) {
|
||||||
|
this.chartHits()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async get() {
|
||||||
|
const s = store.getters.serviceByAll(this.id)
|
||||||
|
window.console.log("service: ", s)
|
||||||
|
this.getService(this.service)
|
||||||
|
this.messages = this.$store.getters.serviceMessages(this.service.id)
|
||||||
|
},
|
||||||
messageInRange(message) {
|
messageInRange(message) {
|
||||||
const start = this.isBetween(new Date(), message.start_on)
|
const start = this.isBetween(this.now(), this.parseTime(message.start_on))
|
||||||
const end = this.isBetween(message.end_on, new Date())
|
const end = this.isBetween(this.parseTime(message.end_on), this.now())
|
||||||
return start && end
|
return start && end
|
||||||
},
|
},
|
||||||
async getService(s) {
|
async getService(s) {
|
||||||
|
@ -255,11 +304,19 @@ export default {
|
||||||
await this.serviceFailures()
|
await this.serviceFailures()
|
||||||
},
|
},
|
||||||
async serviceFailures() {
|
async serviceFailures() {
|
||||||
this.failures = await Api.service_failures(this.service.id, this.now() - 3600, this.now(), 15)
|
let tt = this.startEndTimes()
|
||||||
|
|
||||||
|
this.failures = await Api.service_failures(this.service.id, tt.start, tt.end)
|
||||||
},
|
},
|
||||||
async chartHits() {
|
async chartHits(start=0, end=99999999999, group="30m") {
|
||||||
this.end_time = new Date()
|
let tt = {};
|
||||||
this.data = await Api.service_hits(this.service.id, this.toUnix(this.service.created_at), this.toUnix(new Date()), "30m", false)
|
if (start === 0) {
|
||||||
|
tt = this.startEndTimes()
|
||||||
|
} else {
|
||||||
|
tt = {start, end}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = await Api.service_hits(this.service.id, tt.start, tt.end, group, false)
|
||||||
if (this.data.length === 0 && group !== "1h") {
|
if (this.data.length === 0 && group !== "1h") {
|
||||||
await this.chartHits("1h")
|
await this.chartHits("1h")
|
||||||
}
|
}
|
||||||
|
@ -268,11 +325,15 @@ export default {
|
||||||
...this.convertToChartData(this.data)
|
...this.convertToChartData(this.data)
|
||||||
}]
|
}]
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
},
|
||||||
|
startEndTimes() {
|
||||||
|
const start = this.toUnix(this.parseTime(this.service.stats.first_hit))
|
||||||
|
const end = this.toUnix(new Date())
|
||||||
|
return {start, end}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -27,7 +27,6 @@ const routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
name: 'Dashboard',
|
|
||||||
component: Dashboard,
|
component: Dashboard,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
|
|
|
@ -25,8 +25,7 @@ export default new Vuex.Store({
|
||||||
groups: [],
|
groups: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
users: [],
|
users: [],
|
||||||
notifiers: [],
|
notifiers: []
|
||||||
integrations: []
|
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
hasAllData: state => state.hasAllData,
|
hasAllData: state => state.hasAllData,
|
||||||
|
@ -38,13 +37,19 @@ export default new Vuex.Store({
|
||||||
messages: state => state.messages,
|
messages: state => state.messages,
|
||||||
users: state => state.users,
|
users: state => state.users,
|
||||||
notifiers: state => state.notifiers,
|
notifiers: state => state.notifiers,
|
||||||
integrations: state => state.integrations,
|
|
||||||
|
|
||||||
servicesInOrder: state => state.services.sort((a, b) => a.order_id - b.order_id),
|
servicesInOrder: state => state.services.sort((a, b) => a.order_id - b.order_id),
|
||||||
groupsInOrder: state => state.groups.sort((a, b) => a.order_id - b.order_id),
|
groupsInOrder: state => state.groups.sort((a, b) => a.order_id - b.order_id),
|
||||||
groupsClean: state => state.groups.filter(g => g.name !== '').sort((a, b) => a.order_id - b.order_id),
|
groupsClean: state => state.groups.filter(g => g.name !== '').sort((a, b) => a.order_id - b.order_id),
|
||||||
groupsCleanInOrder: state => state.groups.filter(g => g.name !== '').sort((a, b) => a.order_id - b.order_id).sort((a, b) => a.order_id - b.order_id),
|
groupsCleanInOrder: state => state.groups.filter(g => g.name !== '').sort((a, b) => a.order_id - b.order_id).sort((a, b) => a.order_id - b.order_id),
|
||||||
|
|
||||||
|
serviceByAll: (state) => (element) => {
|
||||||
|
if (element % 1 === 0) {
|
||||||
|
return state.services.find(s => s.id == element)
|
||||||
|
} else {
|
||||||
|
return state.services.find(s => s.permalink === element)
|
||||||
|
}
|
||||||
|
},
|
||||||
serviceById: (state) => (id) => {
|
serviceById: (state) => (id) => {
|
||||||
return state.services.find(s => s.id == id)
|
return state.services.find(s => s.id == id)
|
||||||
},
|
},
|
||||||
|
@ -100,12 +105,13 @@ export default new Vuex.Store({
|
||||||
},
|
},
|
||||||
setNotifiers (state, notifiers) {
|
setNotifiers (state, notifiers) {
|
||||||
state.notifiers = notifiers
|
state.notifiers = notifiers
|
||||||
},
|
|
||||||
setIntegrations (state, integrations) {
|
|
||||||
state.integrations = integrations
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
async getAllServices(context) {
|
||||||
|
const services = await Api.services()
|
||||||
|
context.commit("setServices", services);
|
||||||
|
},
|
||||||
async loadRequired(context) {
|
async loadRequired(context) {
|
||||||
const core = await Api.core()
|
const core = await Api.core()
|
||||||
context.commit("setCore", core);
|
context.commit("setCore", core);
|
||||||
|
@ -140,8 +146,6 @@ export default new Vuex.Store({
|
||||||
context.commit("setNotifiers", notifiers);
|
context.commit("setNotifiers", notifiers);
|
||||||
const users = await Api.users()
|
const users = await Api.users()
|
||||||
context.commit("setUsers", users);
|
context.commit("setUsers", users);
|
||||||
const integrations = await Api.integrations()
|
|
||||||
context.commit("setIntegrations", integrations);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -128,7 +128,11 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
groupQuery := database.ParseQueries(r, service.AllHits())
|
groupQuery, err := database.ParseQueries(r, service.AllHits())
|
||||||
|
if err != nil {
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
objs, err := groupQuery.GraphData(database.ByAverage("latency", 1000))
|
objs, err := groupQuery.GraphData(database.ByAverage("latency", 1000))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -146,7 +150,11 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
groupQuery := database.ParseQueries(r, service.AllFailures())
|
groupQuery, err := database.ParseQueries(r, service.AllFailures())
|
||||||
|
if err != nil {
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
objs, err := groupQuery.GraphData(database.ByCount)
|
objs, err := groupQuery.GraphData(database.ByCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -164,7 +172,11 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
groupQuery := database.ParseQueries(r, service.AllHits())
|
groupQuery, err := database.ParseQueries(r, service.AllHits())
|
||||||
|
if err != nil {
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
objs, err := groupQuery.GraphData(database.ByAverage("ping_time", 1000))
|
objs, err := groupQuery.GraphData(database.ByAverage("ping_time", 1000))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -216,7 +228,11 @@ func apiServiceFailuresHandler(r *http.Request) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fails []*failures.Failure
|
var fails []*failures.Failure
|
||||||
database.ParseQueries(r, service.AllFailures()).Find(&fails)
|
query, err := database.ParseQueries(r, service.AllFailures())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query.Find(&fails)
|
||||||
return fails
|
return fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,6 +244,10 @@ func apiServiceHitsHandler(r *http.Request) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
var hts []*hits.Hit
|
var hts []*hits.Hit
|
||||||
database.ParseQueries(r, service.AllHits()).Find(&hts)
|
query, err := database.ParseQueries(r, service.AllHits())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query.Find(&hts)
|
||||||
return hts
|
return hts
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/statping/statping/utils"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const latestMigration = 1583860000
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
os.Setenv("MIGRATION_ID", utils.ToString(latestMigration))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DbConfig) genericMigration(alterStr string) error {
|
||||||
|
if err := c.Db.Exec(fmt.Sprintf("ALTER TABLE hits %s COLUMN latency TYPE BIGINT;", alterStr)).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(fmt.Sprintf("ALTER TABLE hits %s COLUMN ping_time TYPE BIGINT;", alterStr)).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(fmt.Sprintf("ALTER TABLE failures %s COLUMN latency TYPE BIGINT;", alterStr)).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec("UPDATE hits SET latency = CAST(latency * 1000000 AS bigint);").Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec("UPDATE hits SET ping_time = CAST(ping_time * 1000000 AS bigint);").Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec("UPDATE failures SET ping_time = CAST(ping_time * 1000000 AS bigint);").Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DbConfig) sqliteMigration() error {
|
||||||
|
if err := c.Db.Exec(`ALTER TABLE hits RENAME TO hits_backup;`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(`CREATE TABLE hits (id INTEGER PRIMARY KEY AUTOINCREMENT, service bigint, latency bigint, ping_time bigint, created_at datetime);`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(`INSERT INTO hits (id, service, latency, ping_time, created_at) SELECT id, service, CAST(latency * 1000000 AS bigint), CAST(ping_time * 1000000 AS bigint), created_at FROM hits_backup;`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// failures table
|
||||||
|
if err := c.Db.Exec(`ALTER TABLE failures RENAME TO failures_backup;`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(`CREATE TABLE failures (id INTEGER PRIMARY KEY AUTOINCREMENT, issue varchar(255), method varchar(255), method_id bigint, service bigint, ping_time bigint, checkin bigint, error_code bigint, created_at datetime);`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(`INSERT INTO failures (id, issue, method, method_id, service, ping_time, checkin, created_at) SELECT id, issue, method, method_id, service, CAST(ping_time * 1000000 AS bigint), checkin, created_at FROM failures_backup;`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(`DROP TABLE hits_backup;`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Db.Exec(`DROP TABLE failures_backup;`).Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||||
"github.com/statping/statping/types/checkins"
|
"github.com/statping/statping/types/checkins"
|
||||||
"github.com/statping/statping/types/core"
|
"github.com/statping/statping/types/core"
|
||||||
"github.com/statping/statping/types/failures"
|
"github.com/statping/statping/types/failures"
|
||||||
|
@ -12,53 +15,36 @@ import (
|
||||||
"github.com/statping/statping/types/notifications"
|
"github.com/statping/statping/types/notifications"
|
||||||
"github.com/statping/statping/types/services"
|
"github.com/statping/statping/types/services"
|
||||||
"github.com/statping/statping/types/users"
|
"github.com/statping/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
|
func (c *DbConfig) DatabaseChanges() error {
|
||||||
//func (c *DbConfig) InsertNotifierDB() error {
|
var cr core.Core
|
||||||
// if !database.Available() {
|
c.Db.Model(&core.Core{}).Find(&cr)
|
||||||
// err := c.Connect()
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.New("database connection has not been created")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// notifiers.SetDB(database.DB())
|
|
||||||
// return nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
// InsertIntegratorDB inject the Statping database instance to the Integrations package
|
if latestMigration > cr.MigrationId {
|
||||||
//func (c *DbConfig) InsertIntegratorDB() error {
|
log.Infof("Statping database is out of date, migrating to: %d", latestMigration)
|
||||||
// if !database.Available() {
|
|
||||||
// err := c.Connect()
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.Wrap(err,"database connection has not been created")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// integrations.SetDB(database.DB())
|
|
||||||
// return nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (c *DbConfig) VerifyMigration() error {
|
switch c.Db.DbType() {
|
||||||
|
case "mysql":
|
||||||
|
if err := c.genericMigration("MODIFY"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "postgres":
|
||||||
|
if err := c.genericMigration("ALTER"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := c.sqliteMigration(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query := `
|
if err := c.Db.Exec(fmt.Sprintf("UPDATE core SET migration_id = %d", latestMigration)).Error(); err != nil {
|
||||||
BEGIN TRANSACTION;
|
return err
|
||||||
ALTER TABLE hits ALTER COLUMN latency BIGINT;
|
}
|
||||||
ALTER TABLE hits ALTER COLUMN ping_time BIGINT;
|
|
||||||
ALTER TABLE failures ALTER COLUMN ping_time BIGINT;
|
|
||||||
UPDATE hits SET latency = CAST(latency * 10000 AS BIGINT);
|
|
||||||
UPDATE hits SET ping_time = CAST(ping_time * 100000 AS BIGINT);
|
|
||||||
UPDATE failures SET ping_time = CAST(ping_time * 100000 AS BIGINT);
|
|
||||||
COMMIT;`
|
|
||||||
|
|
||||||
fmt.Println(c.Db.DbType())
|
}
|
||||||
|
return nil
|
||||||
q := c.Db.Raw(query).Debug()
|
|
||||||
|
|
||||||
return q.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//MigrateDatabase will migrate the database structure to current version.
|
//MigrateDatabase will migrate the database structure to current version.
|
||||||
|
|
|
@ -3,12 +3,11 @@ package core
|
||||||
import (
|
import (
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Samples() error {
|
func Samples() error {
|
||||||
apiKey := utils.Getenv("API_KEY", "samplekey")
|
apiKey := utils.Getenv("API_KEY", utils.RandomString(16))
|
||||||
apiSecret := utils.Getenv("API_SECRET", "samplesecret")
|
apiSecret := utils.Getenv("API_SECRET", utils.RandomString(16))
|
||||||
|
|
||||||
core := &Core{
|
core := &Core{
|
||||||
Name: "Statping Sample Data",
|
Name: "Statping Sample Data",
|
||||||
|
@ -16,10 +15,10 @@ func Samples() error {
|
||||||
ApiKey: apiKey.(string),
|
ApiKey: apiKey.(string),
|
||||||
ApiSecret: apiSecret.(string),
|
ApiSecret: apiSecret.(string),
|
||||||
Domain: "http://localhost:8080",
|
Domain: "http://localhost:8080",
|
||||||
Version: "test",
|
CreatedAt: utils.Now(),
|
||||||
CreatedAt: time.Now().UTC(),
|
|
||||||
UseCdn: null.NewNullBool(false),
|
UseCdn: null.NewNullBool(false),
|
||||||
Footer: null.NewNullString(""),
|
Footer: null.NewNullString(""),
|
||||||
|
MigrationId: utils.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return core.Create()
|
return core.Create()
|
||||||
|
|
Loading…
Reference in New Issue