mirror of https://github.com/statping/statping
Merge branch 'checkin-updates' into sass-design
commit
6a0f14c4e3
|
@ -1,8 +1,12 @@
|
||||||
|
# 0.90.60 (07-15-2020)
|
||||||
|
- Added LETSENCRYPT_ENABLE (boolean) env to enable/disable letsencrypt SSL
|
||||||
|
|
||||||
# 0.90.59 (07-14-2020)
|
# 0.90.59 (07-14-2020)
|
||||||
- Added LetsEncrypt SSL Generator by using LETSENCRYPT_HOST and LETSENCRYPT_EMAIL envs.
|
- Added LetsEncrypt SSL Generator by using LETSENCRYPT_HOST and LETSENCRYPT_EMAIL envs.
|
||||||
- Modified JWT token key to be sha256 of API Secret
|
- Modified JWT token key to be sha256 of API Secret
|
||||||
- Modified github actions to build multi-arch Docker images
|
- Modified github actions to build multi-arch Docker images
|
||||||
- Added "update" command to install latest version
|
- Added "update" command to install latest version
|
||||||
|
- Fixed dashboard uptime_data API request to request correct start/time timestamp
|
||||||
|
|
||||||
# 0.90.58 (07-09-2020)
|
# 0.90.58 (07-09-2020)
|
||||||
- Fixed ICMP latency/ping durations
|
- Fixed ICMP latency/ping durations
|
||||||
|
|
|
@ -155,6 +155,10 @@ class Api {
|
||||||
return axios.delete('api/incidents/'+incident.id).then(response => (response.data))
|
return axios.delete('api/incidents/'+incident.id).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkin(api) {
|
||||||
|
return axios.get('api/checkins/'+api).then(response => (response.data))
|
||||||
|
}
|
||||||
|
|
||||||
async checkin_create(data) {
|
async checkin_create(data) {
|
||||||
return axios.post('api/checkins', data).then(response => (response.data))
|
return axios.post('api/checkins', data).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,42 +2,83 @@
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h2>{{service.name}} Checkins</h2>
|
<h2>{{service.name}} Checkins</h2>
|
||||||
<p class="mb-3">Tell your service to send a routine HTTP request to a Statping Checkin.</p>
|
<p class="mb-3">Tell your service to send a routine HTTP request to a Statping Checkin.</p>
|
||||||
<div v-for="(checkin, i) in checkins" class="col-12 alert alert-light" role="alert">
|
|
||||||
<span class="badge badge-pill badge-info text-uppercase">{{checkin.name}}</span>
|
<div v-for="(checkin, i) in checkins" class="card text-black-50 bg-white mt-3">
|
||||||
<span class="float-right font-2">Last checkin {{ago(checkin.last_hit)}}</span>
|
<div class="card-header text-capitalize">
|
||||||
<span class="float-right font-2 mr-3">Check Every {{checkin.interval}} seconds</span>
|
{{checkin.name}}
|
||||||
<span class="float-right font-2 mr-3">Grace Period {{checkin.grace}} seconds</span>
|
<button @click="deleteCheckin(checkin)" class="btn btn-sm btn-danger float-right text-uppercase">Delete</button>
|
||||||
<span class="d-block mt-2">
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" :value="`${core.domain}/checkin/${checkin.api_key}`" readonly>
|
<input type="text" class="form-control" :value="`${core.domain}/checkin/${checkin.api_key}`" readonly>
|
||||||
<span class="small">Send a GET request to this URL every {{checkin.interval}} seconds
|
<div class="input-group-append copy-btn">
|
||||||
<button @click="deleteCheckin(checkin)" type="button" class="btn btn-danger btn-xs float-right mt-1">Delete</button>
|
<button @click.prevent="copy(`${core.domain}/checkin/${checkin.api_key}`)" class="btn btn-outline-secondary" type="button">Copy</button>
|
||||||
</span>
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 alert alert-light">
|
<span class="small">Send a GET request to this URL every {{checkin.interval}} minutes</span>
|
||||||
|
<span class="small float-right mt-1">Requested {{ago(checkin.last_hit)}} ago</span>
|
||||||
|
<span class="small float-right mt-1 mr-3">Request expected every {{checkin.interval}} minutes</span>
|
||||||
|
|
||||||
|
<div class="card text-black-50 bg-white mt-3">
|
||||||
|
<div class="card-header text-capitalize">
|
||||||
|
<font-awesome-icon @click="expanded = !expanded" :icon="expanded ? 'minus' : 'plus'" class="mr-2 pointer"/>
|
||||||
|
{{checkin.name}} Records
|
||||||
|
</div>
|
||||||
|
<div class="card-body" :class="{'d-none': !expanded}">
|
||||||
|
<div class="alert alert-primary small" :class="{'alert-success': hit.success, 'alert-danger': !hit.success}" v-for="(hit, i) in records(checkin)">
|
||||||
|
Checkin {{hit.success ? "Request" : "Failure"}} at {{hit.created_at}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card text-black-50 bg-white mt-3">
|
||||||
|
<div class="card-header text-capitalize">
|
||||||
|
<font-awesome-icon @click="curl_expanded = !curl_expanded" :icon="curl_expanded ? 'minus' : 'plus'" class="mr-2 pointer"/>
|
||||||
|
Cronjob Task
|
||||||
|
</div>
|
||||||
|
<div class="card-body" :class="{'d-none': !curl_expanded}">
|
||||||
|
This cronjob script will request the checkin endpoint every {{checkin.interval}} minutes. Add this cronjob task to the machine running this service.
|
||||||
|
<div class="input-group mt-2">
|
||||||
|
<input type="text" class="form-control" :value="`${checkin.interval} * * * * /usr/bin/curl ${core.domain}/checkin/${checkin.api_key} >/dev/null 2>&1`" readonly>
|
||||||
|
<div class="input-group-append copy-btn">
|
||||||
|
<button @click.prevent="copy(`${checkin.interval} * * * * /usr/bin/curl ${core.domain}/checkin/${checkin.api_key} >/dev/null 2>&1`)" class="btn btn-outline-secondary" type="button">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="small d-block">Using CURL</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<span :class="{'text-success': last_record(checkin).success, 'text-danger': !last_record(checkin).success}">
|
||||||
|
{{last_record(checkin).success ? "Checkin is currently working correctly" : "Checkin is currently failing"}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card text-black-50 bg-white mt-4">
|
||||||
|
<div class="card-header text-capitalize">Create Checkin</div>
|
||||||
|
<div class="card-body">
|
||||||
<form @submit.prevent="saveCheckin">
|
<form @submit.prevent="saveCheckin">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<label for="checkin_interval" class="col-form-label">Checkin Name</label>
|
<label for="checkin_interval" class="col-form-label">Checkin Name</label>
|
||||||
<input v-model="checkin.name" type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
<input v-model="checkin.name" type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="col-3">
|
||||||
<label for="checkin_interval" class="col-form-label">Interval</label>
|
<label for="checkin_interval" class="col-form-label">Interval (minutes)</label>
|
||||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="1" min="1">
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<label for="grace_period" class="col-form-label">Grace Period</label>
|
|
||||||
<input v-model="checkin.grace" type="number" name="grace" class="form-control" id="grace_period" placeholder="10">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<label class="col-form-label"></label>
|
<label class="col-form-label"></label>
|
||||||
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-primary d-block mt-2">Save Checkin</button>
|
<button :disabled="btn_disabled" @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-primary d-block mt-2">Save Checkin</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -49,10 +90,11 @@ export default {
|
||||||
return {
|
return {
|
||||||
service: {},
|
service: {},
|
||||||
ready: false,
|
ready: false,
|
||||||
|
expanded: false,
|
||||||
|
curl_expanded: false,
|
||||||
checkin: {
|
checkin: {
|
||||||
name: "",
|
name: "",
|
||||||
interval: 60,
|
interval: 1,
|
||||||
grace: 60,
|
|
||||||
service_id: 0,
|
service_id: 0,
|
||||||
hits: [],
|
hits: [],
|
||||||
failures: []
|
failures: []
|
||||||
|
@ -66,6 +108,12 @@ export default {
|
||||||
core() {
|
core() {
|
||||||
return this.$store.getters.core
|
return this.$store.getters.core
|
||||||
},
|
},
|
||||||
|
btn_disabled() {
|
||||||
|
if (this.checkin.name === "" || this.checkin.interval <= 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
if (this.$route.params) {
|
if (this.$route.params) {
|
||||||
|
@ -76,22 +124,37 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
records(checkin) {
|
||||||
|
let hits = []
|
||||||
|
let failures = []
|
||||||
|
checkin.hits.forEach((hit) => {
|
||||||
|
hits.push({success: true, created_at: this.parseISO(hit.created_at), id: hit.id})
|
||||||
|
})
|
||||||
|
checkin.failures.forEach((failure) => {
|
||||||
|
failures.push({success: false, created_at: this.parseISO(failure.created_at), id: failure.id})
|
||||||
|
})
|
||||||
|
return hits.concat(failures).sort((a, b) => {return a.created_at-b.created_at}).reverse().slice(0,32)
|
||||||
|
},
|
||||||
|
last_record(checkin) {
|
||||||
|
const r = this.records(checkin)
|
||||||
|
return r[0]
|
||||||
|
},
|
||||||
fixInts() {
|
fixInts() {
|
||||||
const c = this.checkin
|
const c = this.checkin
|
||||||
this.checkin.interval = parseInt(c.interval)
|
this.checkin.interval = parseInt(c.interval)
|
||||||
this.checkin.grace = parseInt(c.grace)
|
|
||||||
return this.checkin
|
return this.checkin
|
||||||
},
|
},
|
||||||
async saveCheckin() {
|
async saveCheckin() {
|
||||||
const c = this.fixInts()
|
const c = this.fixInts()
|
||||||
await Api.checkin_create(c)
|
await Api.checkin_create(c)
|
||||||
await this.updateCheckins()
|
this.checkin.name = ""
|
||||||
|
await this.load()
|
||||||
},
|
},
|
||||||
async deleteCheckin(checkin) {
|
async deleteCheckin(checkin) {
|
||||||
await Api.checkin_delete(checkin)
|
await Api.checkin_delete(checkin)
|
||||||
await this.updateCheckins()
|
await this.load()
|
||||||
},
|
},
|
||||||
async updateCheckins() {
|
async load() {
|
||||||
const checkins = await Api.checkins()
|
const checkins = await Api.checkins()
|
||||||
this.$store.commit('setCheckins', checkins)
|
this.$store.commit('setCheckins', checkins)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="card text-black-50 bg-white mt-3 mb-3">
|
||||||
|
<div class="card-header text-capitalize">Service Latency</div>
|
||||||
|
<div class="card-body">
|
||||||
<div class="service-chart-container">
|
<div class="service-chart-container">
|
||||||
<apexchart width="100%" height="420" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
<apexchart width="100%" height="420" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
<input v-model="checkin.name" type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
<input v-model="checkin.name" type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<label for="checkin_interval" class="col-form-label">Interval</label>
|
<label for="checkin_interval" class="col-form-label">Interval (minutes)</label>
|
||||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="1" min="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<label for="grace_period" class="col-form-label">Grace Period</label>
|
<label for="grace_period" class="col-form-label">Grace Period</label>
|
||||||
|
|
|
@ -85,7 +85,6 @@ export default Vue.mixin({
|
||||||
copy(txt) {
|
copy(txt) {
|
||||||
this.$copyText(txt).then(function (e) {
|
this.$copyText(txt).then(function (e) {
|
||||||
alert('Copied: \n'+txt)
|
alert('Copied: \n'+txt)
|
||||||
console.log(e)
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
serviceLink(service) {
|
serviceLink(service) {
|
||||||
|
|
|
@ -18,16 +18,19 @@
|
||||||
|
|
||||||
<MessageBlock v-for="message in messagesInRange" v-bind:key="message.id" :message="message"/>
|
<MessageBlock v-for="message in messagesInRange" v-bind:key="message.id" :message="message"/>
|
||||||
|
|
||||||
<div class="row mt-5 mb-4">
|
<div class="card text-black-50 bg-white mt-3">
|
||||||
<div class="col-12 col-md-5 font-2 mb-3 mb-md-0">
|
<div class="card-header text-capitalize">Timeframe</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-4 font-2">
|
||||||
<flatPickr :disabled="loading" @on-change="onnn" v-model="start_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="btn btn-white text-left" required />
|
<flatPickr :disabled="loading" @on-change="onnn" v-model="start_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="btn btn-white text-left" required />
|
||||||
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-5 font-2 mb-3 mb-md-0">
|
<div class="col-12 col-md-4 font-2">
|
||||||
<flatPickr :disabled="loading" @on-change="onnn" v-model="end_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date()}" type="text" class="btn btn-white text-left" required />
|
<flatPickr :disabled="loading" @on-change="onnn" v-model="end_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date()}" type="text" class="btn btn-white text-left" required />
|
||||||
<small class="d-block">To {{this.format(new Date(end_time))}}</small>
|
<small class="d-block">To {{this.format(new Date(end_time))}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-2">
|
<div class="col-12 col-md-4">
|
||||||
<select :disabled="loading" @change="chartHits" v-model="group" class="form-control">
|
<select :disabled="loading" @change="chartHits" v-model="group" class="form-control">
|
||||||
<option value="1m">1 Minute</option>
|
<option value="1m">1 Minute</option>
|
||||||
<option value="5m">5 Minutes</option>
|
<option value="5m">5 Minutes</option>
|
||||||
|
@ -44,16 +47,23 @@
|
||||||
<small class="d-block d-md-none d-block">Increment Timeframe</small>
|
<small class="d-block d-md-none d-block">Increment Timeframe</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
||||||
|
|
||||||
<div v-if="!loading" class="col-12">
|
<div v-if="!loading" class="row">
|
||||||
<apexchart width="100%" height="120" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
<apexchart width="100%" height="120" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card text-black-50 bg-white mb-3">
|
||||||
|
<div class="card-header text-capitalize">Service Failures</div>
|
||||||
|
<div class="card-body">
|
||||||
<div class="service-chart-heatmap mt-5 mb-4">
|
<div class="service-chart-heatmap mt-5 mb-4">
|
||||||
<ServiceHeatmap :service="service"/>
|
<ServiceHeatmap :service="service"/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -378,7 +388,7 @@ export default {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
async fetchUptime() {
|
async fetchUptime() {
|
||||||
const uptime = await Api.service_uptime(this.id, this.params.start, this.params.end)
|
const uptime = await Api.service_uptime(this.service.id, this.params.start, this.params.end)
|
||||||
this.uptime_data = this.parse_uptime(uptime)
|
this.uptime_data = this.parse_uptime(uptime)
|
||||||
},
|
},
|
||||||
parse_uptime(timedata) {
|
parse_uptime(timedata) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -106,6 +107,13 @@ func (s Storage) Delete(key string) {
|
||||||
func (s Storage) Set(key string, content []byte, duration time.Duration) {
|
func (s Storage) Set(key string, content []byte, duration time.Duration) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
u, err := url.Parse(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if u.Query().Get("v") != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.items[key] = Item{
|
s.items[key] = Item{
|
||||||
Content: content,
|
Content: content,
|
||||||
Expiration: utils.Now().Add(duration).UnixNano(),
|
Expiration: utils.Now().Add(duration).UnixNano(),
|
||||||
|
|
|
@ -24,8 +24,7 @@ func findCheckin(r *http.Request) (*checkins.Checkin, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
|
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
chks := checkins.All()
|
returnJson(checkins.All(), w, r)
|
||||||
returnJson(chks, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -39,8 +38,7 @@ func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
|
func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var checkin *checkins.Checkin
|
var checkin *checkins.Checkin
|
||||||
err := DecodeJSON(r, &checkin)
|
if err := DecodeJSON(r, &checkin); err != nil {
|
||||||
if err != nil {
|
|
||||||
sendErrorJson(err, w, r)
|
sendErrorJson(err, w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -63,22 +61,27 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
sendErrorJson(err, w, r)
|
sendErrorJson(err, w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Infof("Checking %s was requested", checkin.Name)
|
||||||
|
|
||||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
|
||||||
|
if last := checkin.LastHit(); last == nil {
|
||||||
|
checkin.Start()
|
||||||
|
}
|
||||||
|
|
||||||
hit := &checkins.CheckinHit{
|
hit := &checkins.CheckinHit{
|
||||||
Checkin: checkin.Id,
|
Checkin: checkin.Id,
|
||||||
From: ip,
|
From: ip,
|
||||||
CreatedAt: utils.Now(),
|
CreatedAt: utils.Now(),
|
||||||
}
|
}
|
||||||
log.Infof("Checking %s was requested", checkin.Name)
|
|
||||||
|
|
||||||
err = hit.Create()
|
if err := hit.Create(); err != nil {
|
||||||
if err != nil {
|
|
||||||
sendErrorJson(err, w, r)
|
sendErrorJson(err, w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkin.Failing = false
|
checkin.Failing = false
|
||||||
checkin.LastHitTime = utils.Now()
|
checkin.LastHitTime = utils.Now()
|
||||||
|
|
||||||
sendJsonAction(hit.Id, "update", w, r)
|
sendJsonAction(hit.Id, "update", w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,7 @@ func TestApiCheckinRoutes(t *testing.T) {
|
||||||
Body: `{
|
Body: `{
|
||||||
"name": "Example Checkin",
|
"name": "Example Checkin",
|
||||||
"service_id": 1,
|
"service_id": 1,
|
||||||
"checkin_interval": 300,
|
"interval": 300,
|
||||||
"grace_period": 60,
|
|
||||||
"api_key": "example"
|
"api_key": "example"
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -56,7 +56,7 @@ func RunHTTPServer() error {
|
||||||
resetCookies()
|
resetCookies()
|
||||||
httpError = make(chan error)
|
httpError = make(chan error)
|
||||||
|
|
||||||
if utils.Params.GetString("LETSENCRYPT_HOST") != "" {
|
if utils.Params.GetBool("LETSENCRYPT_ENABLE") {
|
||||||
go startLetsEncryptServer(ip)
|
go startLetsEncryptServer(ip)
|
||||||
} else if usingSSL {
|
} else if usingSSL {
|
||||||
go startSSLServer(ip)
|
go startSSLServer(ip)
|
||||||
|
|
|
@ -64,8 +64,6 @@ func letsEncryptCert() (*tls.Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startLetsEncryptServer(ip string) {
|
func startLetsEncryptServer(ip string) {
|
||||||
log.Infoln("Starting SSL with LetsEncrypt")
|
|
||||||
|
|
||||||
log.Infoln("Starting LetEncrypt redirect server on port 80")
|
log.Infoln("Starting LetEncrypt redirect server on port 80")
|
||||||
go http.ListenAndServe(":80", http.HandlerFunc(simplecert.Redirect))
|
go http.ListenAndServe(":80", http.HandlerFunc(simplecert.Redirect))
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,7 @@ import (
|
||||||
var testCheckin = &Checkin{
|
var testCheckin = &Checkin{
|
||||||
ServiceId: 1,
|
ServiceId: 1,
|
||||||
Name: "Test Checkin",
|
Name: "Test Checkin",
|
||||||
Interval: 60,
|
Interval: 3,
|
||||||
GracePeriod: 10,
|
|
||||||
ApiKey: "tHiSiSaTeStXXX",
|
ApiKey: "tHiSiSaTeStXXX",
|
||||||
CreatedAt: utils.Now(),
|
CreatedAt: utils.Now(),
|
||||||
UpdatedAt: utils.Now(),
|
UpdatedAt: utils.Now(),
|
||||||
|
|
|
@ -15,6 +15,11 @@ func SetDB(database database.Database) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkin) AfterFind() {
|
func (c *Checkin) AfterFind() {
|
||||||
|
c.AllHits = c.Hits()
|
||||||
|
c.AllFailures = c.Failures().LastAmount(64)
|
||||||
|
if last := c.LastHit(); last != nil {
|
||||||
|
c.LastHitTime = last.CreatedAt
|
||||||
|
}
|
||||||
metrics.Query("checkin", "find")
|
metrics.Query("checkin", "find")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,9 +46,6 @@ func (c *Checkin) Create() error {
|
||||||
c.ApiKey = utils.RandomString(32)
|
c.ApiKey = utils.RandomString(32)
|
||||||
}
|
}
|
||||||
q := db.Create(c)
|
q := db.Create(c)
|
||||||
|
|
||||||
c.Start()
|
|
||||||
go c.checkinRoutine()
|
|
||||||
return q.Error()
|
return q.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ package checkins
|
||||||
|
|
||||||
func (c *Checkin) LastHit() *CheckinHit {
|
func (c *Checkin) LastHit() *CheckinHit {
|
||||||
var hit CheckinHit
|
var hit CheckinHit
|
||||||
dbHits.Where("checkin = ?", c.Id).Limit(1).Find(&hit)
|
dbHits.Where("checkin = ?", c.Id).Last(&hit)
|
||||||
return &hit
|
return &hit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkin) Hits() []*CheckinHit {
|
func (c *Checkin) Hits() []*CheckinHit {
|
||||||
var hits []*CheckinHit
|
var hits []*CheckinHit
|
||||||
dbHits.Where("checkin = ?", c.Id).Find(&hits)
|
dbHits.Where("checkin = ?", c.Id).Order("DESC").Find(&hits)
|
||||||
c.AllHits = hits
|
c.AllHits = hits
|
||||||
return hits
|
return hits
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
func (c *Checkin) CreateFailure(f *failures.Failure) error {
|
func (c *Checkin) CreateFailure(f *failures.Failure) error {
|
||||||
f.Checkin = c.Id
|
f.Checkin = c.Id
|
||||||
|
c.Failing = true
|
||||||
return failures.DB().Create(f).Error()
|
return failures.DB().Create(f).Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,22 +10,11 @@ func (c *Checkin) Expected() time.Duration {
|
||||||
last := c.LastHit()
|
last := c.LastHit()
|
||||||
now := utils.Now()
|
now := utils.Now()
|
||||||
lastDir := now.Sub(last.CreatedAt)
|
lastDir := now.Sub(last.CreatedAt)
|
||||||
sub := time.Duration(c.Period() - lastDir)
|
return c.Period() - lastDir
|
||||||
return sub
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkin) Period() time.Duration {
|
func (c *Checkin) Period() time.Duration {
|
||||||
duration, _ := time.ParseDuration(fmt.Sprintf("%ds", c.Interval))
|
return time.Duration(c.Interval) * time.Minute
|
||||||
if duration.Seconds() <= 15 {
|
|
||||||
return 15 * time.Second
|
|
||||||
}
|
|
||||||
return duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response)
|
|
||||||
func (c *Checkin) Grace() time.Duration {
|
|
||||||
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod))
|
|
||||||
return duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start will create a channel for the checkin checking go routine
|
// Start will create a channel for the checkin checking go routine
|
||||||
|
|
|
@ -25,11 +25,8 @@ func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
|
||||||
|
|
||||||
// checkinRoutine for checking if the last Checkin was within its interval
|
// checkinRoutine for checking if the last Checkin was within its interval
|
||||||
func (c *Checkin) checkinRoutine() {
|
func (c *Checkin) checkinRoutine() {
|
||||||
lastHit := c.LastHit()
|
|
||||||
if lastHit == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reCheck := c.Period()
|
reCheck := c.Period()
|
||||||
|
|
||||||
CheckinLoop:
|
CheckinLoop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -38,20 +35,25 @@ CheckinLoop:
|
||||||
c.Failing = false
|
c.Failing = false
|
||||||
break CheckinLoop
|
break CheckinLoop
|
||||||
case <-time.After(reCheck):
|
case <-time.After(reCheck):
|
||||||
log.Infoln(fmt.Sprintf("Checkin '%s' expects a request every %v", c.Name, utils.FormatDuration(c.Period())))
|
lastHit := c.LastHit()
|
||||||
if c.Expected() <= 0 {
|
ago := utils.Now().Sub(lastHit.CreatedAt)
|
||||||
issue := fmt.Sprintf("Checkin '%s' is failing, no request since %v", c.Name, lastHit.CreatedAt)
|
|
||||||
//log.Errorln(issue)
|
log.Infoln(fmt.Sprintf("Checkin '%s' expects a request every %s last request was %s ago", c.Name, c.Period(), utils.DurationReadable(ago)))
|
||||||
|
|
||||||
|
if ago.Seconds() > c.Period().Seconds() {
|
||||||
|
issue := fmt.Sprintf("Checkin expects a request every %d seconds", c.Interval)
|
||||||
|
log.Warnln(issue)
|
||||||
|
|
||||||
fail := &failures.Failure{
|
fail := &failures.Failure{
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Method: "checkin",
|
Method: "checkin",
|
||||||
Service: c.ServiceId,
|
Service: c.ServiceId,
|
||||||
Checkin: c.Id,
|
PingTime: ago.Milliseconds(),
|
||||||
PingTime: c.Expected().Milliseconds(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.CreateFailure(fail)
|
if err := c.CreateFailure(fail); err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reCheck = c.Period()
|
reCheck = c.Period()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,7 @@ func Samples() error {
|
||||||
checkin1 := &Checkin{
|
checkin1 := &Checkin{
|
||||||
Name: "Demo Checkin 1",
|
Name: "Demo Checkin 1",
|
||||||
ServiceId: 1,
|
ServiceId: 1,
|
||||||
Interval: 300,
|
Interval: 3,
|
||||||
GracePeriod: 300,
|
|
||||||
ApiKey: "demoCheckin123",
|
ApiKey: "demoCheckin123",
|
||||||
}
|
}
|
||||||
if err := checkin1.Create(); err != nil {
|
if err := checkin1.Create(); err != nil {
|
||||||
|
@ -21,8 +20,7 @@ func Samples() error {
|
||||||
checkin2 := &Checkin{
|
checkin2 := &Checkin{
|
||||||
Name: "Example Checkin 2",
|
Name: "Example Checkin 2",
|
||||||
ServiceId: 2,
|
ServiceId: 2,
|
||||||
Interval: 900,
|
Interval: 1,
|
||||||
GracePeriod: 300,
|
|
||||||
ApiKey: utils.RandomString(7),
|
ApiKey: utils.RandomString(7),
|
||||||
}
|
}
|
||||||
if err := checkin2.Create(); err != nil {
|
if err := checkin2.Create(); err != nil {
|
||||||
|
|
|
@ -11,7 +11,6 @@ type Checkin struct {
|
||||||
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
|
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
|
||||||
Name string `gorm:"column:name" json:"name"`
|
Name string `gorm:"column:name" json:"name"`
|
||||||
Interval int64 `gorm:"column:check_interval" json:"interval"`
|
Interval int64 `gorm:"column:check_interval" json:"interval"`
|
||||||
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
|
|
||||||
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
|
|
|
@ -57,11 +57,20 @@ func LoadConfigs(cfgFile string) (*DbConfig, error) {
|
||||||
p.Set("API_SECRET", db.ApiSecret)
|
p.Set("API_SECRET", db.ApiSecret)
|
||||||
}
|
}
|
||||||
if db.Language != "" {
|
if db.Language != "" {
|
||||||
p.Set("LANGUAGE", "en")
|
p.Set("LANGUAGE", db.Language)
|
||||||
}
|
}
|
||||||
if db.SendReports {
|
if db.SendReports {
|
||||||
p.Set("ALLOW_REPORTS", true)
|
p.Set("ALLOW_REPORTS", true)
|
||||||
}
|
}
|
||||||
|
if db.LetsEncryptEmail != "" {
|
||||||
|
p.Set("LETSENCRYPT_EMAIL", db.LetsEncryptEmail)
|
||||||
|
}
|
||||||
|
if db.LetsEncryptHost != "" {
|
||||||
|
p.Set("LETSENCRYPT_HOST", db.LetsEncryptHost)
|
||||||
|
}
|
||||||
|
if db.LetsEncryptEnable {
|
||||||
|
p.Set("LETSENCRYPT_ENABLE", db.LetsEncryptEnable)
|
||||||
|
}
|
||||||
|
|
||||||
configs := &DbConfig{
|
configs := &DbConfig{
|
||||||
DbConn: p.GetString("DB_CONN"),
|
DbConn: p.GetString("DB_CONN"),
|
||||||
|
@ -80,6 +89,11 @@ func LoadConfigs(cfgFile string) (*DbConfig, error) {
|
||||||
SqlFile: p.GetString("SQL_FILE"),
|
SqlFile: p.GetString("SQL_FILE"),
|
||||||
Language: p.GetString("LANGUAGE"),
|
Language: p.GetString("LANGUAGE"),
|
||||||
SendReports: p.GetBool("ALLOW_REPORTS"),
|
SendReports: p.GetBool("ALLOW_REPORTS"),
|
||||||
|
LetsEncryptEnable: p.GetBool("LETSENCRYPT_ENABLE"),
|
||||||
|
}
|
||||||
|
if configs.LetsEncryptEnable {
|
||||||
|
configs.LetsEncryptHost = p.GetString("LETSENCRYPT_HOST")
|
||||||
|
configs.LetsEncryptEmail = p.GetString("LETSENCRYPT_EMAIL")
|
||||||
}
|
}
|
||||||
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + cfgFile)
|
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + cfgFile)
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,9 @@ type DbConfig struct {
|
||||||
Error error `yaml:"-" json:"-"`
|
Error error `yaml:"-" json:"-"`
|
||||||
Location string `yaml:"location" json:"-"`
|
Location string `yaml:"location" json:"-"`
|
||||||
SqlFile string `yaml:"sqlfile,omitempty" json:"-"`
|
SqlFile string `yaml:"sqlfile,omitempty" json:"-"`
|
||||||
|
LetsEncryptHost string `yaml:"letsencrypt_host,omitempty" json:"letsencrypt_host"`
|
||||||
|
LetsEncryptEmail string `yaml:"letsencrypt_email,omitempty" json:"letsencrypt_email"`
|
||||||
|
LetsEncryptEnable bool `yaml:"letsencrypt_enable" json:"letsencrypt_enable"`
|
||||||
LocalIP string `yaml:"-" json:"-"`
|
LocalIP string `yaml:"-" json:"-"`
|
||||||
filename string `yaml:"-" json:"-"`
|
filename string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (f Failurer) List() []*Failure {
|
||||||
|
|
||||||
func (f Failurer) LastAmount(amount int) []*Failure {
|
func (f Failurer) LastAmount(amount int) []*Failure {
|
||||||
var fail []*Failure
|
var fail []*Failure
|
||||||
f.db.Order("id asc").Limit(amount).Find(&fail)
|
f.db.Order("id DESC").Limit(amount).Find(&fail)
|
||||||
return fail
|
return fail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,10 @@ import (
|
||||||
// CheckinProcess runs the checkin routine for each checkin attached to service
|
// CheckinProcess runs the checkin routine for each checkin attached to service
|
||||||
func CheckinProcess(s *Service) {
|
func CheckinProcess(s *Service) {
|
||||||
for _, c := range s.Checkins() {
|
for _, c := range s.Checkins() {
|
||||||
|
if last := c.LastHit(); last != nil {
|
||||||
c.Start()
|
c.Start()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Checkins() []*checkins.Checkin {
|
func (s *Service) Checkins() []*checkins.Checkin {
|
||||||
|
|
|
@ -253,10 +253,6 @@ func SelectAllServices(start bool) (map[int64]*Service, error) {
|
||||||
return allServices, nil
|
return allServices, nil
|
||||||
}
|
}
|
||||||
for _, s := range all() {
|
for _, s := range all() {
|
||||||
|
|
||||||
if start {
|
|
||||||
CheckinProcess(s)
|
|
||||||
}
|
|
||||||
s.Failures = s.AllFailures().LastAmount(limitedFailures)
|
s.Failures = s.AllFailures().LastAmount(limitedFailures)
|
||||||
for _, c := range s.Checkins() {
|
for _, c := range s.Checkins() {
|
||||||
s.AllCheckins = append(s.AllCheckins, c)
|
s.AllCheckins = append(s.AllCheckins, c)
|
||||||
|
@ -264,6 +260,9 @@ func SelectAllServices(start bool) (map[int64]*Service, error) {
|
||||||
// collect initial service stats
|
// collect initial service stats
|
||||||
s.UpdateStats()
|
s.UpdateStats()
|
||||||
allServices[s.Id] = s
|
allServices[s.Id] = s
|
||||||
|
if start {
|
||||||
|
CheckinProcess(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return allServices, nil
|
return allServices, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,7 @@ var hit3 = &hits.Hit{
|
||||||
var exmapleCheckin = &checkins.Checkin{
|
var exmapleCheckin = &checkins.Checkin{
|
||||||
ServiceId: 1,
|
ServiceId: 1,
|
||||||
Name: "Example Checkin",
|
Name: "Example Checkin",
|
||||||
Interval: 60,
|
Interval: 3,
|
||||||
GracePeriod: 30,
|
|
||||||
ApiKey: "wdededede",
|
ApiKey: "wdededede",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ func InitEnvs() {
|
||||||
Params.SetDefault("LETSENCRYPT_HOST", "")
|
Params.SetDefault("LETSENCRYPT_HOST", "")
|
||||||
Params.SetDefault("LETSENCRYPT_EMAIL", "")
|
Params.SetDefault("LETSENCRYPT_EMAIL", "")
|
||||||
Params.SetDefault("LETSENCRYPT_LOCAL", false)
|
Params.SetDefault("LETSENCRYPT_LOCAL", false)
|
||||||
|
Params.SetDefault("LETSENCRYPT_ENABLE", false)
|
||||||
Params.SetDefault("LOGS_MAX_COUNT", 5)
|
Params.SetDefault("LOGS_MAX_COUNT", 5)
|
||||||
Params.SetDefault("LOGS_MAX_AGE", 28)
|
Params.SetDefault("LOGS_MAX_AGE", 28)
|
||||||
Params.SetDefault("LOGS_MAX_SIZE", 16)
|
Params.SetDefault("LOGS_MAX_SIZE", 16)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.90.59
|
0.90.60
|
||||||
|
|
Loading…
Reference in New Issue