Merge branch 'checkin-updates' into sass-design

pull/745/head
Hunter Long 2020-07-19 20:15:43 -07:00 committed by GitHub
commit 6a0f14c4e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 261 additions and 162 deletions

View File

@ -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

View File

@ -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))
} }

View File

@ -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)
} }

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View File

@ -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) {

View File

@ -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(),

View File

@ -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)
} }

View File

@ -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"
}`, }`,
}, },

View File

@ -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)

View File

@ -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))

View File

@ -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(),

View File

@ -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()
} }

View File

@ -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
} }

View File

@ -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()
} }

View File

@ -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

View File

@ -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()
} }

View File

@ -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 {

View File

@ -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"`

View File

@ -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)

View File

@ -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:"-"`

View File

@ -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
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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",
} }

View File

@ -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)

View File

@ -1 +1 @@
0.90.59 0.90.60