mirror of https://github.com/statping/statping
checkins, UX, and fixes
parent
8f32e89f4e
commit
bbfb21ec03
|
@ -2,6 +2,9 @@
|
|||
- Added Incident Reporting
|
||||
- Added Cypress tests
|
||||
- Added Github and Google OAuth login (beta)
|
||||
- Added Delete All Failures
|
||||
- Added Checkin form
|
||||
- Added Pushover notifier
|
||||
|
||||
# 0.90.22
|
||||
- Added range input types for integer form fields
|
||||
|
|
|
@ -68,6 +68,10 @@ class Api {
|
|||
return axios.post('api/reorder/services', data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async checkins() {
|
||||
return axios.get('api/checkins').then(response => (response.data))
|
||||
}
|
||||
|
||||
async groups() {
|
||||
return axios.get('api/groups').then(response => (response.data))
|
||||
}
|
||||
|
@ -129,6 +133,14 @@ class Api {
|
|||
return axios.delete('api/incidents/'+incident.id).then(response => (response.data))
|
||||
}
|
||||
|
||||
async checkin_create(data) {
|
||||
return axios.post('api/checkins', data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async checkin_delete(checkin) {
|
||||
return axios.delete('api/checkins/'+checkin.api_key).then(response => (response.data))
|
||||
}
|
||||
|
||||
async messages() {
|
||||
return axios.get('api/messages').then(response => (response.data))
|
||||
}
|
||||
|
|
|
@ -15,13 +15,27 @@ HTML,BODY {
|
|||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
font-size: 8pt;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.copy-btn BUTTON {
|
||||
background-color: white;
|
||||
margin: 6px;
|
||||
height: 26px;
|
||||
font-size: 10pt;
|
||||
padding: 3px 7px;
|
||||
border: 1px solid #a7a7a7;
|
||||
border-radius: 4px;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.dim {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
.slider-info {
|
||||
|
|
|
@ -26,10 +26,6 @@
|
|||
<li v-if="$store.state.admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
||||
<router-link to="/dashboard/logs" class="nav-link">Logs</router-link>
|
||||
</li>
|
||||
|
||||
<li @click="navopen = !navopen" class="nav-item navbar-item">
|
||||
<router-link to="/dashboard/help" class="nav-link">Help</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a href="#" class="nav-link" v-on:click="logout">Logout</a>
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
<FormIncident :service="service" />
|
||||
</div>
|
||||
|
||||
<div v-if="openTab === 'checkin'" class="col-12 mt-4">
|
||||
<Checkin :service="service" />
|
||||
</div>
|
||||
|
||||
<div v-if="openTab === 'failures'" class="col-12 mt-4">
|
||||
<button @click.prevent="deleteFailures" class="btn btn-block btn-outline-secondary delete_failures" :disabled="service.stats.failures === 0">Delete Failures</button>
|
||||
|
||||
|
@ -79,6 +83,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Checkin from '../../forms/Checkin';
|
||||
import FormIncident from '../../forms/Incident';
|
||||
import FormMessage from '../../forms/Message';
|
||||
import ServiceFailures from './ServiceFailures';
|
||||
|
@ -89,6 +94,7 @@
|
|||
export default {
|
||||
name: 'ServiceInfo',
|
||||
components: {
|
||||
Checkin,
|
||||
ServiceFailures,
|
||||
FormIncident,
|
||||
FormMessage,
|
||||
|
|
|
@ -1,23 +1,41 @@
|
|||
<template>
|
||||
<form @submit.prevent="saveCheckin">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<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">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label for="checkin_interval" class="col-form-label">Interval (seconds)</label>
|
||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<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 class="col-3">
|
||||
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-success d-block" style="margin-top: 14px;">Save Checkin</button>
|
||||
</div>
|
||||
<div>
|
||||
<div v-for="(checkin, i) in checkins" class="col-12 alert dim" role="alert">
|
||||
<span class="badge badge-pill badge-info text-uppercase">{{checkin.name}}</span>
|
||||
<span class="float-right font-2">Last checkin {{ago(checkin.last_hit)}}</span>
|
||||
<span class="float-right font-2 mr-3">Check Every {{checkin.interval}} seconds</span>
|
||||
<span class="float-right font-2 mr-3">Grace Period {{checkin.grace}} seconds</span>
|
||||
<span class="d-block mt-2">
|
||||
<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
|
||||
<button @click="deleteCheckin(checkin)" type="button" class="btn btn-danger btn-xs float-right mt-1">Delete</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-12 alert dim">
|
||||
<form @submit.prevent="saveCheckin">
|
||||
<div class="form-group row">
|
||||
<div class="col-5">
|
||||
<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">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<label for="checkin_interval" class="col-form-label">Interval</label>
|
||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
||||
</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 class="col-3">
|
||||
<label class="col-form-label"></label>
|
||||
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-success d-block mt-2">Save Checkin</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -37,20 +55,41 @@
|
|||
name: "",
|
||||
interval: 60,
|
||||
grace: 60,
|
||||
service: this.service.id
|
||||
service_id: this.service.id
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async saveCheckin() {
|
||||
const data = {name: this.group.name, public: this.group.public}
|
||||
await Api.group_create(data)
|
||||
const groups = await Api.groups()
|
||||
this.$store.commit('setGroups', groups)
|
||||
computed: {
|
||||
checkins() {
|
||||
return this.$store.getters.serviceCheckins(this.service.id)
|
||||
},
|
||||
core() {
|
||||
return this.$store.getters.core
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fixInts() {
|
||||
const c = this.checkin
|
||||
this.checkin.interval = parseInt(c.interval)
|
||||
this.checkin.grace = parseInt(c.grace)
|
||||
return this.checkin
|
||||
},
|
||||
async saveCheckin() {
|
||||
const c = this.fixInts()
|
||||
await Api.checkin_create(c)
|
||||
await this.updateCheckins()
|
||||
},
|
||||
async deleteCheckin(checkin) {
|
||||
await Api.checkin_delete(checkin)
|
||||
await this.updateCheckins()
|
||||
},
|
||||
async updateCheckins() {
|
||||
const checkins = await Api.checkins()
|
||||
this.$store.commit('setCheckins', checkins)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -8,11 +8,9 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="card-body bg-light pt-3">
|
||||
|
||||
<div v-for="(update, i) in incident.updates" class="alert alert-light" role="alert">
|
||||
<span class="badge badge-pill badge-info text-uppercase">{{update.type}}</span>
|
||||
<span class="float-right font-2">{{ago(update.created_at)}} ago</span>
|
||||
|
||||
<span class="d-block mt-2">{{update.message}}
|
||||
<button @click="delete_update(update)" type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
|
|
|
@ -5,84 +5,83 @@
|
|||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<form @submit.prevent="saveSetup">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label>Database Connection</label>
|
||||
<select @change="canSubmit" v-model="setup.db_connection" id="db_connection" class="form-control">
|
||||
<option value="sqlite">Sqlite</option>
|
||||
<option value="postgres">Postgres</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label>Host</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_host" id="db_host" type="text" class="form-control" placeholder="localhost">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label>Database Port</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_port" id="db_port" type="text" class="form-control" placeholder="localhost">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label>Username</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_user" id="db_user" type="text" class="form-control" placeholder="root">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label for="db_password">Password</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_password" id="db_password" type="password" class="form-control" placeholder="password123">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label for="db_database">Database</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_database" id="db_database" type="text" class="form-control" placeholder="Database name">
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="saveSetup">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label>Database Connection</label>
|
||||
<select @change="canSubmit" v-model="setup.db_connection" id="db_connection" class="form-control">
|
||||
<option value="sqlite">Sqlite</option>
|
||||
<option value="postgres">Postgres</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label>Host</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_host" id="db_host" type="text" class="form-control" placeholder="localhost">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label>Database Port</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_port" id="db_port" type="text" class="form-control" placeholder="localhost">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label>Username</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_user" id="db_user" type="text" class="form-control" placeholder="root">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label for="db_password">Password</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_password" id="db_password" type="password" class="form-control" placeholder="password123">
|
||||
</div>
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label for="db_database">Database</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_database" id="db_database" type="text" class="form-control" placeholder="Database name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-6">
|
||||
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label>Project Name</label>
|
||||
<input @keyup="canSubmit" v-model="setup.project" id="project" type="text" class="form-control" placeholder="Great Uptime" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Project Name</label>
|
||||
<input @keyup="canSubmit" v-model="setup.project" id="project" type="text" class="form-control" placeholder="Great Uptime" required>
|
||||
<div class="form-group">
|
||||
<label>Project Description</label>
|
||||
<input @keyup="canSubmit" v-model="setup.description" id="description" type="text" class="form-control" placeholder="Great Uptime">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="domain">Domain URL</label>
|
||||
<input @keyup="canSubmit" v-model="setup.domain" type="text" class="form-control" id="domain" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Admin Username</label>
|
||||
<input @keyup="canSubmit" v-model="setup.username" id="username" type="text" class="form-control" placeholder="admin" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Admin Password</label>
|
||||
<input @keyup="canSubmit" v-model="setup.password" id="password" type="password" class="form-control" placeholder="password" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Admin Password</label>
|
||||
<input @keyup="canSubmit" v-model="setup.confirm_password" id="password_confirm" type="password" class="form-control" placeholder="password" required>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="col-12 alert alert-danger">
|
||||
{{error}}
|
||||
</div>
|
||||
|
||||
<button @click.prevent="saveSetup" v-bind:disabled="disabled || loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
|
||||
{{loading ? "Loading..." : "Save Settings"}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Project Description</label>
|
||||
<input @keyup="canSubmit" v-model="setup.description" id="description" type="text" class="form-control" placeholder="Great Uptime">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="domain">Domain URL</label>
|
||||
<input @keyup="canSubmit" v-model="setup.domain" type="text" class="form-control" id="domain" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Admin Username</label>
|
||||
<input @keyup="canSubmit" v-model="setup.username" id="username" type="text" class="form-control" placeholder="admin" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Admin Password</label>
|
||||
<input @keyup="canSubmit" v-model="setup.password" id="password" type="password" class="form-control" placeholder="password" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm Admin Password</label>
|
||||
<input @keyup="canSubmit" v-model="setup.confirm_password" id="password_confirm" type="password" class="form-control" placeholder="password" required>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="col-12 alert alert-danger">
|
||||
{{error}}
|
||||
</div>
|
||||
|
||||
<button @click.prevent="saveSetup" v-bind:disabled="disabled || loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
|
||||
{{loading ? "Loading..." : "Save Settings"}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input v-model="core.api_key" type="text" class="form-control" id="api_key" readonly>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-append copy-btn">
|
||||
<button @click.prevent="copy(core.api_key)" class="btn btn-outline-secondary" type="button">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -86,7 +86,7 @@
|
|||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input v-model="core.api_secret" @focus="$event.target.select()" type="text" class="form-control select-input" id="api_secret" readonly>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-append copy-btn">
|
||||
<button @click="copy(core.api_secret)" class="btn btn-outline-secondary" type="button">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -26,6 +26,7 @@ export default new Vuex.Store({
|
|||
messages: [],
|
||||
users: [],
|
||||
notifiers: [],
|
||||
checkins: [],
|
||||
admin: false
|
||||
},
|
||||
getters: {
|
||||
|
@ -39,6 +40,7 @@ export default new Vuex.Store({
|
|||
incidents: state => state.incidents,
|
||||
users: state => state.users,
|
||||
notifiers: state => state.notifiers,
|
||||
checkins: state => state.checkins,
|
||||
|
||||
isAdmin: state => state.admin,
|
||||
|
||||
|
@ -48,6 +50,9 @@ export default new Vuex.Store({
|
|||
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),
|
||||
|
||||
serviceCheckins: (state) => (id) => {
|
||||
return state.checkins.filter(c => c.service_id === id)
|
||||
},
|
||||
serviceByAll: (state) => (element) => {
|
||||
if (element % 1 === 0) {
|
||||
return state.services.find(s => s.id == element)
|
||||
|
@ -99,6 +104,9 @@ export default new Vuex.Store({
|
|||
setServices (state, services) {
|
||||
state.services = services
|
||||
},
|
||||
setCheckins (state, checkins) {
|
||||
state.checkins = checkins
|
||||
},
|
||||
setGroups (state, groups) {
|
||||
state.groups = groups
|
||||
},
|
||||
|
@ -147,6 +155,8 @@ export default new Vuex.Store({
|
|||
context.commit("setGroups", groups);
|
||||
const services = await Api.services()
|
||||
context.commit("setServices", services);
|
||||
const checkins = await Api.checkins()
|
||||
context.commit("setCheckins", checkins);
|
||||
const messages = await Api.messages()
|
||||
context.commit("setMessages", messages)
|
||||
context.commit("setHasPublicData", true)
|
||||
|
|
|
@ -40,8 +40,7 @@ func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
checkin.ServiceId = service.Id
|
||||
err = checkin.Create()
|
||||
if err != nil {
|
||||
if err := checkin.Create(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
@ -52,7 +51,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
checkin, err := checkins.FindByAPI(vars["api"])
|
||||
if err != nil {
|
||||
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
|
||||
sendErrorJson(fmt.Errorf("checkin %s was not found", vars["api"]), w, r)
|
||||
return
|
||||
}
|
||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
|
@ -60,15 +59,17 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
|||
hit := &checkins.CheckinHit{
|
||||
Checkin: checkin.Id,
|
||||
From: ip,
|
||||
CreatedAt: utils.Now().UTC(),
|
||||
CreatedAt: utils.Now(),
|
||||
}
|
||||
log.Infof("Checking %s was requested", checkin.Name)
|
||||
|
||||
err = hit.Create()
|
||||
if err != nil {
|
||||
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
|
||||
return
|
||||
}
|
||||
checkin.Failing = false
|
||||
checkin.LastHitTime = utils.Now().UTC()
|
||||
checkin.LastHitTime = utils.Now()
|
||||
sendJsonAction(hit.Id, "update", w, r)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,6 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
func apiAllIncidentsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
inc := incidents.All()
|
||||
returnJson(inc, w, r)
|
||||
}
|
||||
|
||||
func apiServiceIncidentsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
incids := incidents.FindByService(utils.ToInt(vars["id"]))
|
||||
|
|
|
@ -146,9 +146,9 @@ func Router() *mux.Router {
|
|||
|
||||
// API CHECKIN Routes
|
||||
api.Handle("/api/checkins", authenticated(apiAllCheckinsHandler, false)).Methods("GET")
|
||||
api.Handle("/api/checkin/{api}", authenticated(apiCheckinHandler, false)).Methods("GET")
|
||||
api.Handle("/api/checkin", authenticated(checkinCreateHandler, false)).Methods("POST")
|
||||
api.Handle("/api/checkin/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
|
||||
api.Handle("/api/checkins", authenticated(checkinCreateHandler, false)).Methods("POST")
|
||||
api.Handle("/api/checkins/{api}", authenticated(apiCheckinHandler, false)).Methods("GET")
|
||||
api.Handle("/api/checkins/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
|
||||
r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler))
|
||||
|
||||
// Static Files Routes
|
||||
|
|
|
@ -20,6 +20,7 @@ func InitNotifiers() {
|
|||
Twilio,
|
||||
Webhook,
|
||||
Mobile,
|
||||
Pushover,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/statping/statping/types/failures"
|
||||
"github.com/statping/statping/types/notifications"
|
||||
"github.com/statping/statping/types/notifier"
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/utils"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
pushoverUrl = "https://api.pushover.net/1/messages.json"
|
||||
)
|
||||
|
||||
var _ notifier.Notifier = (*pushover)(nil)
|
||||
|
||||
type pushover struct {
|
||||
*notifications.Notification
|
||||
}
|
||||
|
||||
func (t *pushover) Select() *notifications.Notification {
|
||||
return t.Notification
|
||||
}
|
||||
|
||||
var Pushover = &pushover{¬ifications.Notification{
|
||||
Method: "pushover",
|
||||
Title: "Pushover",
|
||||
Description: "Use Pushover to receive push notifications. You will need to create a <a href=\"https://pushover.net/apps/build\">New Application</a> on Pushover before using this notifier.",
|
||||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
Icon: "fa dot-circle",
|
||||
Delay: time.Duration(10 * time.Second),
|
||||
Limits: 60,
|
||||
Form: []notifications.NotificationForm{{
|
||||
Type: "text",
|
||||
Title: "User Token",
|
||||
Placeholder: "Insert your device's Pushover Token",
|
||||
DbField: "api_key",
|
||||
Required: true,
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "Application API Key",
|
||||
Placeholder: "Create an Application and insert the API Key here",
|
||||
DbField: "api_secret",
|
||||
Required: true,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
// Send will send a HTTP Post to the Pushover API. It accepts type: string
|
||||
func (t *pushover) sendMessage(message string) error {
|
||||
v := url.Values{}
|
||||
v.Set("token", t.ApiSecret)
|
||||
v.Set("user", t.ApiKey)
|
||||
v.Set("message", message)
|
||||
rb := strings.NewReader(v.Encode())
|
||||
|
||||
_, _, err := utils.HttpRequest(pushoverUrl, "POST", "application/x-www-form-urlencoded", nil, rb, time.Duration(10*time.Second), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// OnFailure will trigger failing service
|
||||
func (t *pushover) OnFailure(s *services.Service, f *failures.Failure) error {
|
||||
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
|
||||
return t.sendMessage(msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (t *pushover) OnSuccess(s *services.Service) error {
|
||||
msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name)
|
||||
return t.sendMessage(msg)
|
||||
}
|
||||
|
||||
// OnTest will test the Pushover SMS messaging
|
||||
func (t *pushover) OnTest() error {
|
||||
msg := fmt.Sprintf("Testing the Pushover Notifier")
|
||||
return t.sendMessage(msg)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/statping/statping/database"
|
||||
"github.com/statping/statping/types/notifications"
|
||||
"github.com/statping/statping/types/null"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
PUSHOVER_TOKEN = os.Getenv("PUSHOVER_TOKEN")
|
||||
PUSHOVER_API = os.Getenv("PUSHOVER_API")
|
||||
)
|
||||
|
||||
func TestPushoverNotifier(t *testing.T) {
|
||||
db, err := database.OpenTester()
|
||||
require.Nil(t, err)
|
||||
db.AutoMigrate(¬ifications.Notification{})
|
||||
notifications.SetDB(db)
|
||||
|
||||
if PUSHOVER_TOKEN == "" || PUSHOVER_API == "" {
|
||||
t.Log("Pushover notifier testing skipped, missing PUSHOVER_TOKEN and PUSHOVER_API environment variable")
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
t.Run("Load Pushover", func(t *testing.T) {
|
||||
Pushover.ApiKey = PUSHOVER_TOKEN
|
||||
Pushover.ApiSecret = PUSHOVER_API
|
||||
Pushover.Enabled = null.NewNullBool(true)
|
||||
|
||||
Add(Pushover)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", Pushover.Author)
|
||||
assert.Equal(t, PUSHOVER_TOKEN, Pushover.ApiKey)
|
||||
})
|
||||
|
||||
t.Run("Pushover Within Limits", func(t *testing.T) {
|
||||
assert.True(t, Pushover.CanSend())
|
||||
})
|
||||
|
||||
t.Run("Pushover OnFailure", func(t *testing.T) {
|
||||
err := Pushover.OnFailure(exampleService, exampleFailure)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Pushover OnSuccess", func(t *testing.T) {
|
||||
err := Pushover.OnSuccess(exampleService)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Pushover Test", func(t *testing.T) {
|
||||
err := Pushover.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
}
|
|
@ -32,7 +32,7 @@ func All() []*Checkin {
|
|||
}
|
||||
|
||||
func (c *Checkin) Create() error {
|
||||
c.ApiKey = utils.RandomString(7)
|
||||
c.ApiKey = utils.RandomString(32)
|
||||
q := db.Create(c)
|
||||
|
||||
c.Start()
|
||||
|
|
|
@ -57,7 +57,7 @@ func Connect(configs *DbConfig, retry bool) error {
|
|||
if err != nil {
|
||||
log.Debugln(fmt.Sprintf("Database connection error %s", err))
|
||||
if retry {
|
||||
log.Errorln(fmt.Sprintf("Database %s connection to '%s' is not available, trying again in 5 seconds...", configs.DbConn, configs.DbHost))
|
||||
log.Warnln(fmt.Sprintf("Database %s connection to '%s' is not available, trying again in 5 seconds...", configs.DbConn, configs.DbHost))
|
||||
time.Sleep(5 * time.Second)
|
||||
return Connect(configs, retry)
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue