notifier updates for UX, UI updates, cypress testing

pull/490/head
hunterlong 2020-04-10 22:59:51 -07:00
parent 9d396e012d
commit 74d77b6300
27 changed files with 373 additions and 127 deletions

View File

@ -1,3 +1,9 @@
# 0.90.25
- Added string response on OnTest for Notifiers
- Modified UI to show user the response for a Notifier.
- Modified some Notifiers title's
- Added more Cypress e2e testing
# 0.90.24
- Fixed login form from not showing

View File

@ -29,7 +29,7 @@ context('Groups Tests', () => {
cy.visit('/dashboard/services')
cy.get('.sortable_groups > tr').should('have.length', 3)
cy.get('.sortable_groups > tr').eq(0).contains('PRIVATE')
cy.get('.sortable_groups > tr').eq(0).contains('PUBLIC')
cy.get('.sortable_groups > tr').eq(1).contains('PUBLIC')
cy.get('.sortable_groups > tr').eq(2).contains('PRIVATE')
})
@ -48,17 +48,28 @@ context('Groups Tests', () => {
cy.get('button[type="submit"]').click()
})
it('should edit Group', () => {
cy.visit('/dashboard/services')
cy.get('.sortable_groups > tr').eq(0).find('.btn-outline-secondary').click()
cy.get('#title').should('have.value', 'Test Group')
cy.get('#title').clear().type('Updated Group')
cy.get('button[type="submit"]').click()
cy.get('.sortable_groups > tr').eq(0).contains('Updated Group')
})
it('should confirm new groups', () => {
cy.visit('/dashboard/services')
cy.get('.sortable_groups > tr').should('have.length', 5)
cy.get('.sortable_groups > tr').eq(0).contains('PUBLIC')
cy.get('.sortable_groups > tr').eq(0).contains('Test Group')
cy.get('.sortable_groups > tr').eq(0).contains('Updated Group')
cy.get('.sortable_groups > tr').eq(1).contains('PRIVATE')
cy.get('.sortable_groups > tr').eq(1).contains('Test Private Group')
})
it('should delete new groups', () => {
cy.visit('/dashboard/services')
cy.get('.sortable_groups > tr').eq(0).find('.btn-danger').click()
cy.get('.sortable_groups > tr').eq(1).find('.btn-danger').click()
cy.get('.sortable_groups > tr').should('have.length', 3)

View File

@ -2,7 +2,7 @@
import "../support/commands"
context('Messages Tests', () => {
context('Annoucements Tests', () => {
beforeEach(() => {
@ -43,4 +43,10 @@ context('Messages Tests', () => {
cy.get('tbody > tr').should('have.length', 3)
})
it('should confirm delete Message', () => {
cy.visit('/dashboard/messages')
cy.get('tbody > tr').eq(0).find('.btn-danger').click()
cy.get('tbody > tr').should('have.length', 2)
})
})

View File

@ -25,12 +25,26 @@ context('Notifier Tests', () => {
cy.getCookies().should('have.length', 1)
})
// uzrwstmtd69hi4wgzsj27q2v29mtpu
it('should confirm notifiers are installed', () => {
cy.visit('/dashboard/settings')
cy.get('#notifiers_tabs > a').should('have.length', 9)
cy.get('#notifiers_tabs > a').should('have.length', 10)
cy.get('#api_key').should('not.have.value', '')
cy.get('#api_secret').should('not.have.value', '')
})
it('should test and save notifier', () => {
cy.visit('/dashboard/settings')
cy.get('#notifiers_tabs > a').should('have.length', 10)
cy.get('#notifiers_tabs > #v-pills-command-tab').click()
cy.get('#v-pills-command-tab > .form-control').eq(0).clear().type('/bin/sh')
cy.get('#v-pills-command-tab > .form-control').eq(1).clear().type('echo "success"')
cy.get('#v-pills-command-tab > .form-control').eq(2).clear().type('echo "failure"')
cy.get('#v-pills-command-tab > .card-body > .btn').eq(0).click()
cy.get('#v-pills-command-tab > .card-body > .btn').eq(1).click()
})
})

View File

@ -26,7 +26,7 @@ context('Services Tests', () => {
it('should goto services', () => {
cy.visit('/dashboard/services')
cy.get('#services_list > tr').should('have.length', 5)
cy.get('#services_list > tr').should('have.length', 6)
cy.get('.sortable_groups > tr').should('have.length', 3)
})
@ -79,7 +79,7 @@ context('Services Tests', () => {
it('should create new ICMP service', () => {
cy.visit('/dashboard/create_service')
cy.get('#name').clear().type('ICMP Service')
cy.get('#name').clear().type('ICMP Service')
cy.get('#service_type').select('icmp')
cy.get('#service_url').clear().type('8.8.8.8')
@ -93,16 +93,16 @@ context('Services Tests', () => {
it('should confirm new services', () => {
cy.visit('/dashboard/services')
cy.get('#services_list > tr').should('have.length', 9)
cy.get('#services_list > tr').should('have.length', 10)
})
it('should delete new services', () => {
cy.visit('/dashboard/services')
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
cy.get('#services_list > tr').eq(0).find('.btn-danger').click()
cy.get('#services_list > tr').should('have.length', 4)
cy.get('#services_list > tr').eq(0).find('a.btn-danger').click()
cy.get('#services_list > tr').eq(1).find('a.btn-danger').click()
cy.get('#services_list > tr').eq(2).find('a.btn-danger').click()
cy.get('#services_list > tr').eq(3).find('a.btn-danger').click()
cy.get('#services_list > tr').should('have.length', 6)
})
})

View File

@ -27,7 +27,7 @@ context('Settings Tests', () => {
it('should confirm notifiers are installed', () => {
cy.visit('/dashboard/settings')
cy.get('#notifiers_tabs > a').should('have.length', 9)
cy.get('#notifiers_tabs > a').should('have.length', 10)
cy.get('#api_key').should('not.have.value', '')
cy.get('#api_secret').should('not.have.value', '')
@ -36,7 +36,7 @@ context('Settings Tests', () => {
it('should update Statping settings', () => {
cy.visit('/dashboard/settings')
cy.get('#project').clear().type('Statping Cypress Tests')
cy.get('#project').clear().type('Statping Updated')
cy.get('#description').clear().type('Statping can use Cypress e2e testing to make it more stable!')
cy.get('#domain').clear().type('http://localhost:8888')
cy.get('#footer').clear().type('Statping Custom Footer')
@ -46,7 +46,7 @@ context('Settings Tests', () => {
it('should confirm Statping settings', () => {
cy.visit('/dashboard/settings')
cy.get('#project').should('have.value', 'Statping Cypress Tests')
cy.get('#project').should('have.value', 'Statping Updated')
cy.get('#description').should('have.value', 'Statping can use Cypress e2e testing to make it more stable!')
cy.get('#domain').should('have.value', 'http://localhost:8888')
cy.get('#footer').should('have.value', 'Statping Custom Footer')
@ -59,6 +59,13 @@ context('Settings Tests', () => {
cy.get('.footer').should('contain', 'Statping Custom Footer')
})
it('should regenerate API Keys', () => {
cy.visit('/dashboard/settings')
cy.get('#regenkeys').click()
cy.get('#api_key').should('not.have.value', '')
cy.get('#api_secret').should('not.have.value', '')
})
it('should create Local Assets', () => {
cy.visit('/dashboard/settings')
cy.get('#v-pills-style-tab').click()

View File

@ -39,13 +39,52 @@ context('Users Tests', () => {
cy.get('#password_confirm').clear().type('password123')
cy.get('button[type="submit"]').click()
cy.get('#users_table > tr').should('have.length', 2)
})
// it('should confirm new user', () => {
// cy.visit('/dashboard/users')
// cy.get('#users_table > tr').should('have.length', 2)
// cy.get('#users_table > tr').eq(0).contains('admin')
// cy.get('#users_table > tr').eq(1).contains('admin2')
// })
it('should create new Admin User', () => {
cy.visit('/dashboard/users')
cy.get('#username').clear().type('admin3')
cy.get('#admin_switch').click()
cy.get('#email').clear().type('info@admin3.com')
cy.get('#password').clear().type('password123')
cy.get('#password_confirm').clear().type('password123')
cy.get('button[type="submit"]').click()
cy.get('#users_table > tr').should('have.length', 3)
})
it('should confirm new user', () => {
cy.visit('/dashboard/users')
cy.get('#users_table > tr').should('have.length', 3)
cy.get('#users_table > tr').eq(0).contains('admin')
cy.get('#users_table > tr').eq(1).contains('admin2')
cy.get('#users_table > tr').eq(2).contains('admin3')
cy.get('#users_table > tr').eq(0).contains('ADMIN')
cy.get('#users_table > tr').eq(1).contains('USER')
cy.get('#users_table > tr').eq(2).contains('ADMIN')
})
it('should confirm edit user', () => {
cy.visit('/dashboard/users')
cy.get('#users_table > tr').should('have.length', 3)
cy.get('#users_table > tr').eq(2).find('a.edit-user').click()
cy.get('#email').should('have.value', 'info@admin3.com')
cy.get('#email').clear().type('info@updated.com')
cy.get('#password').type('password123')
cy.get('#password_confirm').type('password123')
cy.get('button[type="submit"]').click()
cy.get('#users_table > tr').should('have.length', 3)
})
it('should delete new users', () => {
cy.visit('/dashboard/users')
cy.get('#users_table > tr').should('have.length', 3)
cy.get('#users_table > tr').eq(2).find('a.btn-danger').click()
cy.get('#users_table > tr').eq(1).find('a.btn-danger').click()
cy.get('#users_table > tr').should('have.length', 1)
})
})

View File

@ -14,15 +14,22 @@
</thead>
<tbody id="users_table">
<tr v-for="(user, index) in users" v-bind:key="index" >
<tr v-for="(user, index) in users" v-bind:key="user.id" >
<td>{{user.username}}</td>
<td v-if="user.admin"><span class="badge badge-danger">ADMIN</span></td>
<td v-if="!user.admin"><span class="badge badge-primary">USER</span></td>
<td>
<span class="badge" :class="{'badge-danger': user.admin, 'badge-primary': !user.admin}">
{{user.admin ? 'ADMIN' : 'USER'}}
</span>
</td>
<td class="d-none d-md-table-cell">{{niceDate(user.updated_at)}}</td>
<td class="text-right">
<div class="btn-group">
<a @click.prevent="editUser(user, edit)" href="" class="btn btn-outline-secondary"><font-awesome-icon icon="user" /> Edit</a>
<a @click.prevent="deleteUser(user)" v-if="index !== 0" href="" class="btn btn-danger"><font-awesome-icon icon="times" /></a>
<a @click.prevent="editUser(user, edit)" class="btn btn-outline-secondary edit-user">
<font-awesome-icon icon="user" /> Edit
</a>
<a @click.prevent="deleteUser(user)" v-if="index !== 0" class="btn btn-danger delete-user">
<font-awesome-icon icon="times" />
</a>
</div>
</td>
</tr>

View File

@ -0,0 +1,45 @@
<template>
<button v-html="loading ? loadLabel : label" @click.prevent="runAction" type="submit" :disabled="loading || disabled" class="btn btn-block" :class="{class: !loading, 'btn-outline-light': loading}">
</button>
</template>
<script>
export default {
name: 'LoadButton',
props: {
action: {
type: Function,
required: true
},
label: {
type: String,
required: true
},
class: {
type: String,
default: "btn-primary"
},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
loading: false,
loadLabel: "<div class=\"spinner-border text-dark\"><span class=\"sr-only\">Loading</span></div>"
}
},
methods: {
async runAction() {
this.loading = true;
await this.action();
this.loading = false;
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -35,8 +35,13 @@
</div>
</div>
<div v-if="error" class="alert alert-danger col-12" role="alert">{{error}}</div>
<div v-if="success" class="alert alert-success col-12" role="alert">{{notifier.title}} appears to be working!</div>
<div v-if="error && !success" class="alert alert-danger col-12" role="alert">
{{error}}<p v-if="response">Response:<br>{{response}}</p>
</div>
<div v-if="success" class="alert alert-success col-12" role="alert">
{{notifier.title}} appears to be working!
<p v-if="response">Response:<br>{{response}}</p>
</div>
<div class="card text-black-50 bg-white mb-3">
<div class="card-body">
@ -79,6 +84,7 @@ export default {
loading: false,
loadingTest: false,
error: null,
response: null,
success: false,
saved: false,
form: {},
@ -130,6 +136,7 @@ export default {
} else {
this.error = tested.error
}
this.response = tested.response
this.loadingTest = false
},
}

View File

@ -13,9 +13,9 @@
<input v-model="user.username" type="text" class="form-control" id="username" placeholder="Username" required autocorrect="off" autocapitalize="none" v-bind:readonly="user.id">
</div>
<div class="col-6 col-md-4">
<span @click="user.admin = !!user.admin" class="switch">
<input v-model="user.admin" type="checkbox" class="switch" id="switch-normal" v-bind:checked="user.admin">
<label for="switch-normal">Administrator</label>
<span id="admin_switch" @click="user.admin = !!user.admin" class="switch">
<input v-model="user.admin" type="checkbox" class="switch" id="user_admin_switch" v-bind:checked="user.admin">
<label for="user_admin_switch">Administrator</label>
</span>
</div>
</div>
@ -39,11 +39,12 @@
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" @click="saveUser"
<LoadButton
class="btn-primary"
:disabled="loading || !user.username || !user.email || !user.password || !user.confirm_password || (user.password !== user.confirm_password)"
class="btn btn-block" :class="{'btn-primary': !user.id, 'btn-secondary': user.id}">
{{loading ? "Loading..." : user.id ? "Update User" : "Create User"}}
</button>
:action="saveUser"
:label="user.id ? 'Update User' : 'Create User'"
/>
</div>
</div>
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
@ -54,10 +55,12 @@
<script>
import Api from "../API";
import LoadButton from "@/components/Elements/LoadButton";
export default {
name: 'FormUser',
props: {
components: {LoadButton},
props: {
in_user: {
type: Object
},
@ -89,8 +92,7 @@
this.user = {}
this.edit(false)
},
async saveUser(e) {
e.preventDefault();
async saveUser() {
this.loading = true
if (this.user.id) {
await this.updateUser()
@ -103,8 +105,7 @@
let user = this.user
delete user.confirm_password
await Api.user_create(user)
const users = await Api.users()
this.$store.commit('setUsers', users)
await this.update()
this.user = {}
},
async updateUser() {
@ -114,9 +115,12 @@
}
delete user.confirm_password
await Api.user_update(user)
await this.update()
this.edit(false)
},
async update() {
const users = await Api.users()
this.$store.commit('setUsers', users)
this.edit(false)
}
}
}

View File

@ -21,8 +21,8 @@
<h6 class="mt-4 text-muted">Notifiers</h6>
<div id="notifiers_tabs">
<a v-for="(notifier, index) in $store.getters.notifiers" v-bind:key="`${notifier.method}_${index}`" @click.prevent="changeTab" class="nav-link text-capitalize" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" data-toggle="pill" v-bind:href="`#v-pills-${notifier.method.toLowerCase()}`" role="tab" v-bind:aria-controls="`v-pills-${notifier.method.toLowerCase()}`" aria-selected="false">
<font-awesome-icon :icon="iconName(notifier.icon)" class="mr-2"/> {{notifier.method}}
<a v-for="(notifier, index) in notifiers" v-bind:key="`${notifier.method}_${index}`" @click.prevent="changeTab" class="nav-link text-capitalize" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" data-toggle="pill" v-bind:href="`#v-pills-${notifier.method.toLowerCase()}`" role="tab" v-bind:aria-controls="`v-pills-${notifier.method.toLowerCase()}`" aria-selected="false">
<font-awesome-icon :icon="iconName(notifier.icon)" class="mr-2"/> {{notifier.title}}
<span v-if="notifier.enabled" class="badge badge-pill float-right mt-1" :class="{'badge-success': !liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), 'badge-light': liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), 'text-dark': liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}">ON</span>
</a>
</div>
@ -90,7 +90,7 @@
</div>
</div>
<small class="form-text text-muted">API Secret is used for read, create, update and delete routes</small>
<small class="form-text text-muted">You can <a href="#" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small>
<small class="form-text text-muted">You can <a href="#" id="regenkeys" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small>
</div>
</div>
</div>
@ -129,7 +129,7 @@
<OAuth :oauth="core.oauth"/>
</div>
<div v-for="(notifier, index) in notifiers" v-bind:key="`${notifier.title}_${index}`" class="tab-pane fade" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), show: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" role="tabpanel" v-bind:aria-labelledby="`v-pills-${notifier.method.toLowerCase()}-tab`">
<div v-for="(notifier, index) in notifiers" v-bind:key="`${notifier.method}_${index}`" class="tab-pane fade" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), show: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" role="tabpanel" v-bind:aria-labelledby="`v-pills-${notifier.method.toLowerCase()}-tab`">
<Notifier :notifier="notifier"/>
</div>

View File

@ -72,16 +72,18 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
}
notif := services.ReturnNotifier(notifer.Method)
err = notif.OnTest()
out, err := notif.OnTest()
resp := &notifierTestResp{
Success: err == nil,
Error: err,
Success: err == nil,
Response: out,
Error: err,
}
returnJson(resp, w, r)
}
type notifierTestResp struct {
Success bool `json:"success"`
Error error `json:"error,omitempty"`
Success bool `json:"success"`
Response string `json:"response,omitempty"`
Error error `json:"error,omitempty"`
}

View File

@ -22,7 +22,7 @@ func (c *commandLine) Select() *notifications.Notification {
var Command = &commandLine{&notifications.Notification{
Method: "command",
Title: "Shell Command",
Title: "Command",
Description: "Shell Command allows you to run a customized shell/bash Command on the local machine it's running on.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
@ -33,37 +33,30 @@ var Command = &commandLine{&notifications.Notification{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Shell or Bash",
Placeholder: "/bin/bash",
Placeholder: "/usr/bin/curl",
DbField: "host",
SmallText: "You can use '/bin/sh', '/bin/bash' or even an absolute path for an application.",
SmallText: "You can use '/bin/sh', '/bin/bash', '/usr/bin/curl' or an absolute path for an application.",
}, {
Type: "text",
Title: "Command to Run on OnSuccess",
Placeholder: "curl google.com",
Placeholder: "http://localhost:8080/health",
DbField: "var1",
SmallText: "This Command will run every time a service is receiving a Successful event.",
SmallText: "This Command will run when a service is receiving a Successful event.",
}, {
Type: "text",
Title: "Command to Run on OnFailure",
Placeholder: "curl offline.com",
Placeholder: "http://localhost:8080/health",
DbField: "var2",
SmallText: "This Command will run every time a service is receiving a Failing event.",
SmallText: "This Command will run when a service is receiving a Failing event.",
}}},
}
func runCommand(app string, cmd ...string) (string, string, error) {
utils.Log.Infof("Command notifier sending: %s %s", app, strings.Join(cmd, " "))
outStr, errStr, err := utils.Command(app, cmd...)
return outStr, errStr, err
}
// OnFailure for commandLine will trigger failing service
func (c *commandLine) OnFailure(s *services.Service, f *failures.Failure) error {
msg := c.GetValue("var2")
tmpl := ReplaceVars(msg, s, f)
_, _, err := runCommand(c.Host, tmpl)
return err
}
// OnSuccess for commandLine will trigger successful service
func (c *commandLine) OnSuccess(s *services.Service) error {
msg := c.GetValue("var1")
@ -72,11 +65,19 @@ func (c *commandLine) OnSuccess(s *services.Service) error {
return err
}
// OnTest for commandLine triggers when this notifier has been saved
func (c *commandLine) OnTest() error {
cmds := strings.Split(c.Var1, " ")
in, out, err := runCommand(c.Host, cmds...)
utils.Log.Infoln(in)
utils.Log.Infoln(out)
// OnFailure for commandLine will trigger failing service
func (c *commandLine) OnFailure(s *services.Service, f *failures.Failure) error {
msg := c.GetValue("var2")
tmpl := ReplaceVars(msg, s, f)
_, _, err := runCommand(c.Host, tmpl)
return err
}
// OnTest for commandLine triggers when this notifier has been saved
func (c *commandLine) OnTest() (string, error) {
tmpl := ReplaceVars(c.Var1, exampleService, exampleFailure)
in, out, err := runCommand(c.Host, tmpl)
utils.Log.Infoln(in)
utils.Log.Infoln(out)
return out, err
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/notifier"
@ -61,23 +60,22 @@ func (d *discord) OnSuccess(s *services.Service) error {
}
// OnSave triggers when this notifier has been saved
func (d *discord) OnTest() error {
func (d *discord) OnTest() (string, error) {
outError := errors.New("Incorrect discord URL, please confirm URL is correct")
message := `{"content": "Testing the discord notifier"}`
contents, _, err := utils.HttpRequest(Discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true)
if string(contents) == "" {
return nil
return "", nil
}
var dtt discordTestJson
err = json.Unmarshal(contents, &dtt)
if err != nil {
return outError
return string(contents), outError
}
if dtt.Code == 0 {
return outError
return string(contents), outError
}
fmt.Println("discord: ", string(contents))
return nil
return string(contents), nil
}
type discordTestJson struct {

View File

@ -186,7 +186,7 @@ func (e *emailer) OnSuccess(s *services.Service) error {
}
// OnTest triggers when this notifier has been saved
func (e *emailer) OnTest() error {
func (e *emailer) OnTest() (string, error) {
testService := &services.Service{
Id: 1,
Name: "Example Service",
@ -201,14 +201,16 @@ func (e *emailer) OnTest() error {
LastResponse: "<html>this is an example response</html>",
CreatedAt: utils.Now().Add(-24 * time.Hour),
}
subject := fmt.Sprintf("Service %v is Back Online", testService.Name)
email := &emailOutgoing{
To: e.Var2,
Subject: fmt.Sprintf("Service %v is Back Online", testService.Name),
Subject: subject,
Template: mainEmailTemplate,
Data: testService,
From: e.Var1,
}
return e.dialSend(email)
err := e.dialSend(email)
return subject, err
}
func (e *emailer) dialSend(email *emailOutgoing) error {

View File

@ -43,28 +43,31 @@ var LineNotify = &lineNotifier{&notifications.Notification{
}
// Send will send a HTTP Post with the Authorization to the notify-api.line.me server. It accepts type: string
func (l *lineNotifier) sendMessage(message string) error {
func (l *lineNotifier) sendMessage(message string) (string, error) {
v := url.Values{}
v.Set("message", message)
headers := []string{fmt.Sprintf("Authorization=Bearer %v", l.ApiSecret)}
_, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true)
return err
content, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true)
return string(content), err
}
// OnFailure will trigger failing service
func (l *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
return l.sendMessage(msg)
_, err := l.sendMessage(msg)
return err
}
// OnSuccess will trigger successful service
func (l *lineNotifier) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf("Service %s is online!", s.Name)
return l.sendMessage(msg)
_, err := l.sendMessage(msg)
return err
}
// OnTest triggers when this notifier has been saved
func (l *lineNotifier) OnTest() error {
func (l *lineNotifier) OnTest() (string, error) {
msg := fmt.Sprintf("Testing if Line Notifier is working!")
return l.sendMessage(msg)
_, err := l.sendMessage(msg)
return msg, err
}

View File

@ -26,7 +26,7 @@ func (m *mobilePush) Select() *notifications.Notification {
var Mobile = &mobilePush{&notifications.Notification{
Method: "mobile",
Title: "Mobile Notifications",
Title: "Mobile",
Description: `Receive push notifications on your Mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the Mobile app setup in seconds.
<p align="center"><a href="https://play.google.com/store/apps/details?id=com.statping"><img src="https://img.cjx.io/google-play.svg"></a><a href="https://itunes.apple.com/us/app/apple-store/id1445513219"><img src="https://img.cjx.io/app-store-badge.svg"></a></p>`,
Author: "Hunter Long",
@ -97,7 +97,7 @@ func (m *mobilePush) OnSuccess(s *services.Service) error {
}
// OnTest triggers when this notifier has been saved
func (m *mobilePush) OnTest() error {
func (m *mobilePush) OnTest() (string, error) {
msg := &pushArray{
Message: "Testing the Mobile Notifier",
Title: "Testing Notifications",
@ -107,18 +107,18 @@ func (m *mobilePush) OnTest() error {
}
body, err := pushRequest(msg)
if err != nil {
return err
return "", err
}
var output mobileResponse
err = json.Unmarshal(body, &output)
if err != nil {
return err
return string(body), err
}
if len(output.Logs) == 0 {
return nil
return string(body), err
} else {
firstLog := output.Logs[0].Error
return fmt.Errorf("Mobile Notification error: %v", firstLog)
return string(body), fmt.Errorf("Mobile Notification error: %v", firstLog)
}
}

View File

@ -0,0 +1,85 @@
package notifiers
import (
"github.com/statping/statping/types/services"
"github.com/stretchr/testify/assert"
"os"
"testing"
)
func TestAllNotifiers(t *testing.T) {
notifiers := []notifierTest{
{
Notifier: Command,
RequiredENV: nil,
},
{
Notifier: Discorder,
RequiredENV: []string{"DISCORD_URL"},
},
{
Notifier: email,
RequiredENV: []string{"EMAIL_HOST", "EMAIL_USER", "EMAIL_PASS", "EMAIL_OUTGOING", "EMAIL_SEND_TO", "EMAIL_PORT"},
},
{
Notifier: Mobile,
RequiredENV: []string{"MOBILE_ID", "MOBILE_NUMBER"},
},
{
Notifier: Pushover,
RequiredENV: []string{"PUSHOVER_TOKEN", "PUSHOVER_API"},
},
{
Notifier: slacker,
RequiredENV: []string{"SLACK_URL"},
},
{
Notifier: Telegram,
RequiredENV: []string{"TELEGRAM_TOKEN", "TELEGRAM_CHANNEL"},
},
{
Notifier: Twilio,
RequiredENV: []string{"TWILIO_SID", "TWILIO_SECRET", "TWILIO_FROM", "TWILIO_TO"},
},
{
Notifier: Webhook,
RequiredENV: nil,
},
}
for _, n := range notifiers {
if !getEnvs(n.RequiredENV) {
t.Skip()
continue
}
Add(n.Notifier)
err := n.Notifier.OnSuccess(exampleService)
assert.Nil(t, err)
err = n.Notifier.OnFailure(exampleService, exampleFailure)
assert.Nil(t, err)
err = n.Notifier.OnTest()
assert.Nil(t, err)
}
}
func getEnvs(env []string) bool {
for _, v := range env {
if os.Getenv(v) == "" {
return false
}
}
return true
}
type notifierTest struct {
Notifier services.ServiceNotifier
RequiredENV []string
}

View File

@ -52,34 +52,37 @@ var Pushover = &pushover{&notifications.Notification{
}
// Send will send a HTTP Post to the Pushover API. It accepts type: string
func (t *pushover) sendMessage(message string) error {
func (t *pushover) sendMessage(message string) (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)
content, _, 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
}
return err
return string(content), 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)
_, err := t.sendMessage(msg)
return err
}
// 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)
_, err := t.sendMessage(msg)
return err
}
// OnTest will test the Pushover SMS messaging
func (t *pushover) OnTest() error {
func (t *pushover) OnTest() (string, error) {
msg := fmt.Sprintf("Testing the Pushover Notifier")
return t.sendMessage(msg)
content, err := t.sendMessage(msg)
return content, err
}

View File

@ -58,16 +58,16 @@ func (s *slack) sendSlack(msg string) error {
return nil
}
func (s *slack) OnTest() error {
func (s *slack) OnTest() (string, error) {
contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second), true)
if err != nil {
return err
return "", err
}
defer resp.Body.Close()
if string(contents) != "ok" {
return errors.New("the slack response was incorrect, check the URL")
return string(contents), errors.New("the slack response was incorrect, check the URL")
}
return nil
return string(contents), nil
}
// OnFailure will trigger failing service

View File

@ -51,7 +51,7 @@ var Telegram = &telegram{&notifications.Notification{
}
// Send will send a HTTP Post to the Telegram API. It accepts type: string
func (t *telegram) sendMessage(message string) error {
func (t *telegram) sendMessage(message string) (string, error) {
apiEndpoint := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", t.ApiSecret)
v := url.Values{}
@ -65,27 +65,30 @@ func (t *telegram) sendMessage(message string) error {
if !success {
errorOut := telegramError(contents)
out := fmt.Sprintf("Error code %v - %v", errorOut.ErrorCode, errorOut.Description)
return errors.New(out)
return string(contents), errors.New(out)
}
return err
return string(contents), err
}
// OnFailure will trigger failing service
func (t *telegram) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
return t.sendMessage(msg)
_, err := t.sendMessage(msg)
return err
}
// OnSuccess will trigger successful service
func (t *telegram) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name)
return t.sendMessage(msg)
_, err := t.sendMessage(msg)
return err
}
// OnTest will test the Twilio SMS messaging
func (t *telegram) OnTest() error {
func (t *telegram) OnTest() (string, error) {
msg := fmt.Sprintf("Testing the Twilio SMS Notifier on your Statping server")
return t.sendMessage(msg)
content, err := t.sendMessage(msg)
return content, err
}
func telegramSuccess(res []byte) (bool, telegramResponse) {

View File

@ -61,7 +61,7 @@ var Twilio = &twilio{&notifications.Notification{
}
// Send will send a HTTP Post to the Twilio SMS API. It accepts type: string
func (t *twilio) sendMessage(message string) error {
func (t *twilio) sendMessage(message string) (string, error) {
twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", t.GetValue("api_key"))
v := url.Values{}
@ -75,25 +75,27 @@ func (t *twilio) sendMessage(message string) error {
if !success {
errorOut := twilioError(contents)
out := fmt.Sprintf("Error code %v - %v", errorOut.Code, errorOut.Message)
return errors.New(out)
return string(contents), errors.New(out)
}
return err
return string(contents), err
}
// OnFailure will trigger failing service
func (t *twilio) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
return t.sendMessage(msg)
_, err := t.sendMessage(msg)
return err
}
// OnSuccess will trigger successful service
func (t *twilio) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name)
return t.sendMessage(msg)
_, err := t.sendMessage(msg)
return err
}
// OnTest will test the Twilio SMS messaging
func (t *twilio) OnTest() error {
func (t *twilio) OnTest() (string, error) {
msg := fmt.Sprintf("Testing the Twilio SMS Notifier")
return t.sendMessage(msg)
}

View File

@ -26,7 +26,7 @@ type webhooker struct {
var Webhook = &webhooker{&notifications.Notification{
Method: webhookMethod,
Title: "HTTP webhooker",
Title: "Webhook",
Description: "Send a custom HTTP request to a specific URL with your own body, headers, and parameters.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
@ -113,16 +113,17 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
return resp, err
}
func (w *webhooker) OnTest() error {
func (w *webhooker) OnTest() (string, error) {
body := ReplaceVars(w.Var2, exampleService, exampleFailure)
resp, err := w.sendHttpWebhook(body)
if err != nil {
return err
return "", err
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
utils.Log.Infoln(fmt.Sprintf("Webhook notifier received: '%v'", string(content)))
return err
out := fmt.Sprintf("Webhook notifier received: '%v'", string(content))
utils.Log.Infoln(out)
return out, err
}
// OnFailure will trigger failing service

View File

@ -9,5 +9,5 @@ import (
type Notifier interface {
OnSuccess(*services.Service) error // OnSuccess is triggered when a service is successful
OnFailure(*services.Service, *failures.Failure) error // OnFailure is triggered when a service is failing
OnTest() error // OnTest is triggered for testing
OnTest() (string, error) // OnTest is triggered for testing
}

View File

@ -28,6 +28,6 @@ func FindNotifier(method string) *notifications.Notification {
type ServiceNotifier interface {
OnSuccess(*Service) error // OnSuccess is triggered when a service is successful
OnFailure(*Service, *failures.Failure) error // OnFailure is triggered when a service is failing
OnTest() error // OnTest is triggered for testing
OnTest() (string, error) // OnTest is triggered for testing
Select() *notifications.Notification // OnTest is triggered for testing
}

View File

@ -1 +1 @@
0.90.24
0.90.25