pull/429/head
hunterlong 2020-03-10 01:49:57 -07:00
parent e0288b21a4
commit de6cf2407f
16 changed files with 251 additions and 80 deletions

View File

@ -3,10 +3,10 @@ RUN npm install yarn -g
WORKDIR /statping WORKDIR /statping
COPY ./frontend/package.json . COPY ./frontend/package.json .
COPY ./frontend/yarn.lock . COPY ./frontend/yarn.lock .
RUN yarn install RUN yarn install --pure-lockfile --network-timeout 1000000
COPY ./frontend . COPY ./frontend .
RUN yarn build && rm -rf node_modules RUN yarn build && rm -rf node_modules && yarn cache clean
# Compiles webpacked Vue production build for frontend at /statping/dist
# Statping Golang BACKEND building from source # Statping Golang BACKEND building from source
# Creates "/go/bin/statping" and "/usr/local/bin/sass" for copying # Creates "/go/bin/statping" and "/usr/local/bin/sass" for copying

View File

@ -1,36 +1,31 @@
FROM golang:1.14-alpine FROM node:10.17.0 AS frontend
RUN npm install yarn -g
WORKDIR /statping
COPY ./frontend/package.json .
COPY ./frontend/yarn.lock .
RUN yarn install --pure-lockfile --network-timeout 1000000
COPY ./frontend .
RUN yarn build && rm -rf node_modules && yarn cache clean
# Statping Golang BACKEND building from source
# Creates "/go/bin/statping" and "/usr/local/bin/sass" for copying
FROM golang:1.14-alpine AS backend
LABEL maintainer="Hunter Long (https://github.com/hunterlong)" LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
ARG VERSION ARG VERSION
RUN apk add --update --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass nodejs nodejs-npm RUN apk add --update --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq
RUN npm install -g yarn
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \ RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
chmod +x /usr/local/bin/sass chmod +x /usr/local/bin/sass
WORKDIR /go/src/github.com/statping/statping WORKDIR /go/src/github.com/statping/statping
ADD go.mod go.sum ./ ADD go.mod go.sum ./
RUN go mod download RUN go mod download
ENV GO111MODULE on ENV GO111MODULE on
RUN go get github.com/stretchr/testify/assert && \ RUN go get github.com/stretchr/testify/assert && \
go get github.com/stretchr/testify/require && \ go get github.com/stretchr/testify/require && \
go get github.com/GeertJohan/go.rice/rice && \ go get github.com/GeertJohan/go.rice/rice && \
go get github.com/cortesi/modd/cmd/modd && \ go get github.com/cortesi/modd/cmd/modd && \
go get github.com/crazy-max/xgo go get github.com/crazy-max/xgo
COPY . .
ADD frontend/package.json frontend/yarn.lock ./frontend/ COPY --from=frontend /statping/dist/ ./source/dist/
RUN make clean generate embed build
RUN cd frontend && yarn install --pure-lockfile --network-timeout 1000000 && \
yarn cache clean
COPY . ./
RUN make clean
RUN make compile
RUN make build
RUN chmod a+x statping && mv statping /go/bin/statping RUN chmod a+x statping && mv statping /go/bin/statping
WORKDIR /app

View File

@ -58,8 +58,8 @@ func databaseMaintence(dur time.Duration) {
// DeleteAllSince will delete a specific table's records based on a time. // DeleteAllSince will delete a specific table's records based on a time.
func DeleteAllSince(table string, date time.Time) { func DeleteAllSince(table string, date time.Time) {
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, database.FormatTime(date)) sql := fmt.Sprintf("DELETE FROM %s WHERE created_at < '%s';", table, database.FormatTime(date))
q := database.Exec(sql) q := database.Exec(sql).Debug()
if q.Error() != nil { if q.Error() != nil {
log.Warnln(q.Error()) log.Warnln(q.Error())
} }

View File

@ -5,14 +5,18 @@ HTML,BODY {
background-color: $background-color; background-color: $background-color;
} }
.index-chart {
height: 35vh;
}
.chartmarker { .chartmarker {
background-color: white;
padding: 5px; padding: 5px;
width: 240px;
text-align: right;
} }
.chartmarker SPAN { .chartmarker SPAN {
font-size: 11pt; font-size: 9pt;
display: block; display: block;
color: #8b8b8b; color: #8b8b8b;
} }
@ -153,9 +157,17 @@ HTML,BODY {
font-size: 9pt; font-size: 9pt;
} }
.font-3 { .font-3 {
font-size: 11pt; font-size: 11pt;
} }
.font-4 {
font-size: 14pt;
}
.font-5 {
font-size: 20pt;
}
.badge { .badge {
color: white; color: white;
@ -222,7 +234,7 @@ HTML,BODY {
.chart-container { .chart-container {
position: relative; position: relative;
height: 24.1vh; height: 18.6vh;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
} }

View File

@ -6,8 +6,8 @@
background-color: $sm-background-color; background-color: $sm-background-color;
} }
.index_container { .index-chart {
padding-top: 4.5vh !important; height: 33vh;
} }
.sm-container { .sm-container {
@ -60,7 +60,7 @@
.btn-sm { .btn-sm {
line-height: 1.4rem; line-height: 1.4rem;
font-size: 1rem; font-size: 0.83rem;
} }
.full-col-12 { .full-col-12 {
@ -73,6 +73,8 @@
border-radius: $sm-border-radius; border-radius: $sm-border-radius;
padding: $sm-padding; padding: $sm-padding;
background-color: $sm-service-background; background-color: $sm-service-background;
box-shadow: 0px 3px 10px 2px;
color: #b5b5b5;
} }
.card-body { .card-body {

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="col-12 full-col-12"> <div class="col-12 full-col-12">
<h4 v-if="group.name !== 'Empty Group'" class="group_header mb-2 mt-4">{{group.name}}</h4> <h4 v-if="group.name !== 'Empty Group'" class="group_header mb-3 mt-4">{{group.name}}</h4>
<div class="list-group online_list mb-4"> <div class="list-group online_list mb-4">
<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">

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="mb-4"> <div class="mb-4">
<div class="card"> <div class="card index-chart">
<div class="card-body"> <div class="card-body">
<div class="col-12"> <div class="col-12">
<h4 class="mt-3"> <h4 class="mt-3">

View File

@ -25,9 +25,6 @@
grid: { grid: {
show: false show: false
}, },
marker: {
show: true
}
}; };
export default { export default {
@ -88,29 +85,36 @@
labels: { labels: {
show: false show: false
}, },
tooltip: {
enabled: false
}
}, },
yaxis: { yaxis: {
labels: { labels: {
show: false show: false
}, },
}, },
markers: {
size: 0,
strokeWidth: 0,
hover: {
size: undefined,
sizeOffset: 0
}
},
tooltip: { tooltip: {
theme: false, theme: false,
enabled: true, enabled: true,
markers: {
size: 0
},
custom: function({series, seriesIndex, dataPointIndex, w}) { custom: function({series, seriesIndex, dataPointIndex, w}) {
let service = w.globals.seriesNames[0]; let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
let ts = w.globals; const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
window.console.log(ts);
let val = series[seriesIndex][dataPointIndex]; let val = series[seriesIndex][dataPointIndex];
if (val > 1000) { if (val >= 1000) {
val = (val * 0.1).toFixed(0) + " milliseconds" val = (val * 0.1).toFixed(0) + " milliseconds"
} else { } else {
val = (val * 0.01).toFixed(0) + " microseconds" val = (val * 0.01).toFixed(0) + " microseconds"
} }
return `<div class="chartmarker"><span>${service} Average Response</span> <span class="font-3">${val}</span></div>` return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
}, },
fixed: { fixed: {
enabled: true, enabled: true,
@ -121,6 +125,9 @@
x: { x: {
show: false, show: false,
}, },
y: {
formatter: (value) => { return value + "%" },
},
}, },
legend: { legend: {
show: false, show: false,

View File

@ -1,20 +1,51 @@
<template v-if="service"> <template v-if="service">
<div class="col-12 card mb-3" style="min-height: 260px" :class="{'offline-card': !service.online}"> <div class="col-12 card mb-4" style="min-height: 360px" :class="{'offline-card': !service.online}">
<div class="card-body"> <div class="card-body p-3 p-md-1 pt-md-3 pb-md-1">
<h5 class="card-title"><router-link :to="serviceLink(service)">{{service.name}}</router-link> <h4 class="card-title mb-4"><router-link :to="serviceLink(service)">{{service.name}}</router-link>
<span class="badge float-right" :class="{'badge-success': service.online, 'badge-danger': !service.online}"> <span class="badge float-right" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
{{service.online ? "ONLINE" : "OFFLINE"}} {{service.online ? "ONLINE" : "OFFLINE"}}
</span> </span>
</h5> </h4>
<transition name="fade">
<div v-if="loaded && service.online" class="row"> <div v-if="loaded && service.online" class="row">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12 mt-2 mt-md-0">
<ServiceSparkLine :title="set1_name" subtitle="Last Day Latency" :series="set1"/> <ServiceSparkLine :title="set2_name" subtitle="Latency Last 24 Hours" :series="set2"/>
</div> </div>
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12 mt-4 mt-md-0">
<ServiceSparkLine :title="set2_name" subtitle="Last 7 Days Latency" :series="set2"/> <ServiceSparkLine :title="set1_name" subtitle="Latency Last 7 Days" :series="set1"/>
</div>
<div class="d-none row col-12 mt-4 pt-1 mb-3 align-content-center">
<StatsGen :service="service"
title="Since Yesterday"
:start="this.toUnix(this.nowSubtract(86400 * 2))"
:end="this.toUnix(this.nowSubtract(86400))"
group="24h" expression="latencyPercent"/>
<StatsGen :service="service"
title="7 Day Change"
:start="this.toUnix(this.nowSubtract(86400 * 7))"
:end="this.toUnix(this.now())"
group="24h" expression="latencyPercent"/>
<StatsGen :service="service"
title="Max Latency"
:start="this.toUnix(this.nowSubtract(86400 * 2))"
:end="this.toUnix(this.nowSubtract(86400))"
group="24h" expression="latencyPercent"/>
<StatsGen :service="service"
title="Uptime"
:start="this.toUnix(this.nowSubtract(86400 * 2))"
:end="this.toUnix(this.nowSubtract(86400))"
group="24h" expression="latencyPercent"/>
</div> </div>
</div> </div>
</transition>
</div> </div>
<span v-for="(failure, index) in failures" v-bind:key="index" class="alert alert-light"> <span v-for="(failure, index) in failures" v-bind:key="index" class="alert alert-light">
Failed {{failure.created_at}}<br> Failed {{failure.created_at}}<br>
{{failure.issue}} {{failure.issue}}
@ -26,10 +57,12 @@
<script> <script>
import ServiceSparkLine from "./ServiceSparkLine"; import ServiceSparkLine from "./ServiceSparkLine";
import Api from "../../API"; import Api from "../../API";
import StatsGen from "./StatsGen";
export default { export default {
name: 'ServiceInfo', name: 'ServiceInfo',
components: { components: {
StatsGen,
ServiceSparkLine ServiceSparkLine
}, },
props: { props: {
@ -49,34 +82,37 @@
} }
}, },
async mounted() { async mounted() {
this.set1 = await this.getHits(24, "15m") this.set1 = await this.getHits(24 * 7, "6h")
this.set1_name = this.calc(this.set1) this.set1_name = this.calc(this.set1)
this.set2 = await this.getHits(24 * 7, "6h") this.set2 = await this.getHits(24, "1h")
this.set2_name = this.calc(this.set2) this.set2_name = this.calc(this.set2)
this.loaded = true this.loaded = true
window.console.log(this.set1)
}, },
methods: { methods: {
sinceYesterday(data) {
window.console.log(data)
let total = 0
data.forEach((f) => {
total += parseInt(f.y)
});
total = total / data.length
},
async getHits(hours, group) { async getHits(hours, group) {
const start = this.nowSubtract(3600 * hours) const start = this.nowSubtract(3600 * hours)
if (!this.service.online) {
this.failures = await Api.service_failures(this.service.id, this.toUnix(start), this.toUnix(this.now()), 5)
return [{name: "None", data: []}]
}
const fetched = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(this.now()), group, false) const fetched = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(this.now()), group, false)
if (!data) {
return [{name: "None", data: []}] const data = this.convertToChartData(fetched, 0.001, true)
}
const data = this.convertToChartData(fetched, 1000, true) return [{name: "Latency", ...data}]
return [{name: "Latency", data}]
}, },
calc(s) { calc(s) {
let data = s[0].data let data = s[0].data
if (data) { if (data) {
let total = 0 let total = 0
data.forEach((f) => { data.forEach((f) => {
total += f.y total += parseInt(f.y)
}); });
total = total / data.length total = total / data.length
return total.toFixed(0) + "ms" return total.toFixed(0) + "ms"

View File

@ -1,9 +1,12 @@
<template v-if="series.length"> <template v-if="series.length">
<apexchart width="100%" height="180" type="area" :options="chartOpts" :series="series"></apexchart> <apexchart width="100%" height="180" type="bar" :options="chartOpts" :series="series"></apexchart>
</template> </template>
<script> <script>
export default { const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
export default {
name: 'ServiceSparkLine', name: 'ServiceSparkLine',
props: { props: {
series: { series: {
@ -29,7 +32,7 @@ export default {
return { return {
chartOpts: { chartOpts: {
chart: { chart: {
type: 'area', type: 'bar',
height: 180, height: 180,
sparkline: { sparkline: {
enabled: true enabled: true
@ -44,9 +47,33 @@ export default {
yaxis: { yaxis: {
min: 0 min: 0
}, },
colors: ['#DCE6EC'], colors: ['#b3bdc3'],
tooltip: { tooltip: {
enabled: false 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: -5,
offsetY: 0,
},
x: {
show: false,
},
y: {
formatter: (value) => { return value + "%" },
},
}, },
title: { title: {
text: this.title, text: this.title,

View File

@ -0,0 +1,70 @@
<template>
<div class="col-3 text-center">
<span class="text-success font-5 font-weight-bold">{{value}}</span>
<span class="font-2 d-block">{{title}}</span>
</div>
</template>
<script>
import Api from "../../API";
export default {
name: 'StatsGen',
props: {
service: {
type: Object,
required: true
},
title: {
type: String,
required: true
},
start: {
type: Number,
required: true
},
end: {
type: Number,
required: true
},
group: {
type: String,
required: true
},
expression: {
type: String,
required: true
}
},
data() {
return {
value: "+17%"
}
},
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>

View File

@ -61,7 +61,7 @@
this.error = true this.error = true
} else if (auth.token) { } else if (auth.token) {
this.auth = Api.saveToken(this.username, auth.token) this.auth = Api.saveToken(this.username, auth.token)
await this.$store.dispatch('loadAdmin') this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard') this.$router.push('/dashboard')
} }
this.loading = false this.loading = false

View File

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

View File

@ -35,8 +35,15 @@ func Select() (*Core, error) {
} }
func (c *Core) Create() error { func (c *Core) Create() error {
//apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(40)) c.ApiKey = c.ApiKey
//apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(40)) c.ApiSecret = c.ApiSecret
apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(40)).(string)
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(40)).(string)
if c.ApiKey == "" || c.ApiSecret == "" {
c.ApiSecret = apiSecret
c.ApiKey = apiKey
}
newCore := &Core{ newCore := &Core{
Name: c.Name, Name: c.Name,
Description: c.Description, Description: c.Description,

View File

@ -22,6 +22,20 @@ func Samples() error {
for i := int64(1); i <= 4; i++ { for i := int64(1); i <= 4; i++ {
sg.Add(1) sg.Add(1)
f1 := &Failure{
Service: i,
Issue: "Server failure",
CreatedAt: utils.Now().Add(-time.Duration(3*i) * 86400),
}
f1.Create()
f2 := &Failure{
Service: i,
Issue: "Server failure",
CreatedAt: utils.Now().Add(-time.Duration(5*i) * 12400),
}
f2.Create()
log.Infoln(fmt.Sprintf("Adding %v Failure records to service", 400)) log.Infoln(fmt.Sprintf("Adding %v Failure records to service", 400))
go func() { go func() {

View File

@ -37,6 +37,7 @@ func All() map[int64]*Service {
func AllInOrder() []Service { func AllInOrder() []Service {
var services []Service var services []Service
for _, service := range allServices { for _, service := range allServices {
service.UpdateStats()
services = append(services, *service) services = append(services, *service)
} }
sort.Sort(ServiceOrder(services)) sort.Sort(ServiceOrder(services))