notify after x failures, variable replacer, 0.90.16

pull/458/head v0.90.16
hunterlong 2020-03-22 19:50:30 -07:00
parent 11538c38e3
commit f331e9bbf9
39 changed files with 514 additions and 540 deletions

View File

@ -1,21 +0,0 @@
on: [push, pull_request]
name: Golang Test
jobs:
test:
env:
GOPATH: ${{ github.workspace }}
GO111MODULE: on
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v1
with:
go-version: 1.14.x
- name: Checkout code
uses: actions/checkout@v2
with:
path: ./src/github.com/${{ github.repository }}
- name: Go Mod
run: go mod download
- name: Test
run: go test -p=1 ./...

View File

@ -1,3 +1,11 @@
# 0.90.16
- Added Notify After (int) field for Services. Will send notifications after x amount of failures.
- Added new method in utils package for replacing `{{.Service.*}}` and `{{.Failure.*}}` variables from string to it's true value
- Fixed Notifer get endpoint
- Cleaned Notifier methods
- Updated recommended changes from [sonarcloud.io](https://sonarcloud.io/organizations/statping/projects)
- Organized utils package files
# 0.90.15
- Fixed /dashboard authentication state to show admin tabs if your an admin. [Issue #438](https://github.com/statping/statping/issues/438)
- Fixed Cache JS error on Dashboard

View File

@ -469,7 +469,6 @@ HTML,BODY {
margin: 0;
font: inherit;
overflow: auto;
font-family: inherit;
display: block;
width: 100%;
padding: 0px;
@ -485,7 +484,6 @@ HTML,BODY {
/* Code Mirror Settings */
font-family: monospace;
position: relative;
overflow: hidden;
height:80vh;
}

View File

@ -138,7 +138,7 @@
name: c.name, description: c.description, domain: c.domain,
timezone: c.timezone, using_cdn: c.using_cdn, footer: c.footer, update_notify: c.update_notify,
gh_client_id: c.github_clientId, gh_client_secret: c.github_clientSecret
}
};
await Api.core_save(coreForm)
const core = await Api.core()
this.$store.commit('setCore', core)

View File

@ -1,5 +1,5 @@
<template>
<form @submit.prevent="login">
<form @submit.prevent="login" autocomplete="on">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">

View File

@ -47,7 +47,7 @@
<div class="col-12 col-sm-12 mt-3">
<button @click.prevent="testNotifier" class="btn btn-secondary btn-block text-capitalize col-12 float-right"><i class="fa fa-vial"></i>
{{loading ? "Loading..." : "Test Notifier"}}</button>
{{loadingTest ? "Loading..." : "Test Notifier"}}</button>
</div>
</div>
@ -74,6 +74,7 @@ export default {
data() {
return {
loading: false,
loadingTest: false,
error: null,
saved: false,
ok: false,
@ -104,10 +105,11 @@ export default {
},
async testNotifier() {
this.ok = false
this.loading = true
this.loadingTest = true
let form = {}
this.notifier.form.forEach((f) => {
form[f.field] = this.notifier[f.field]
let field = f.field.toLowerCase()
this.form[field] = this.notifier[field]
});
form.enabled = this.notifier.enabled
form.limits = parseInt(this.notifier.limits)
@ -118,7 +120,7 @@ export default {
} else {
this.error = tested
}
this.loading = false
this.loadingTest = false
},
}
}

View File

@ -139,6 +139,13 @@
</span>
</div>
</div>
<div v-if="service.allow_notifications" class="form-group row">
<label class="col-sm-4 col-form-label">Notify After Failures</label>
<div class="col-sm-8">
<input v-model="service.notify_after" type="number" name="notify_after" class="form-control" id="notify_after" autocapitalize="none">
<small class="form-text text-muted">Send Notification after {{service.notify_after === 0 ? 'the first Failure' : service.notify_after+' Failures'}} </small>
</div>
</div>
<div v-if="service.allow_notifications" class="form-group row">
<label class="col-sm-4 col-form-label">Notify All Changes</label>
<div class="col-8 mt-1">
@ -196,6 +203,7 @@
verify_ssl: true,
allow_notifications: true,
notify_all_changes: true,
notify_after: 2,
public: true,
},
groups: [],

View File

@ -1,7 +1,7 @@
<template>
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
<div class="col-12 col-md-8 offset-md-2 mb-4">
<img class="col-12 mt-5 mt-md-0" style="max-width:680px" src="/banner.png">
<img alt="Statping Setup" class="col-12 mt-5 mt-md-0" style="max-width:680px" src="banner.png">
</div>
<div class="col-12">

View File

@ -2,7 +2,7 @@
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
<div class="col-10 offset-1 col-md-8 offset-md-2 mt-md-2">
<div class="col-12 col-md-8 offset-md-2 mb-4">
<img class="col-12 mt-5 mt-md-0" style="max-width:680px" src="/banner.png">
<img alt="Statping Login" class="col-12 mt-5 mt-md-0" style="max-width:680px" src="banner.png">
</div>
<FormLogin/>

View File

@ -169,7 +169,7 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
objName = "incident_update"
objId = v.Id
default:
objName = "missing"
objName = fmt.Sprintf("%T", v)
}
output := apiResponse{

View File

@ -17,6 +17,7 @@ package handlers
import (
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/statping/statping/types/notifications"
@ -28,21 +29,21 @@ import (
func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
notifiers := services.AllNotifiers()
var notifs []*notifications.Notification
for _, n := range notifiers {
notifs = append(notifs, notifications.SelectNotifier(n.Select()))
}
returnJson(notifs, w, r)
returnJson(notifiers, w, r)
}
func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
notifier, err := notifications.Find(vars["notifier"])
if err != nil {
sendErrorJson(err, w, r)
return
notifier := vars["notifier"]
notifiers := services.AllNotifiers()
for _, n := range notifiers {
notf := n.Select()
if notifier == notf.Method {
returnJson(n, w, r)
return
}
}
returnJson(notifier, w, r)
sendErrorJson(errors.New("notifier not found"), w, r)
}
func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
@ -66,7 +67,7 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
return
}
//notifications.OnSave(notifer.Method)
sendJsonAction(notifer, "update", w, r)
sendJsonAction(vars["notifier"], "update", w, r)
}
func testNotificationHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -2,6 +2,7 @@ package handlers
import (
"github.com/statping/statping/notifiers"
"github.com/statping/statping/types/services"
"github.com/stretchr/testify/assert"
"testing"
)
@ -17,22 +18,23 @@ func TestApiNotifiersRoutes(t *testing.T) {
URL: "/api/notifiers",
Method: "GET",
ExpectedStatus: 200,
ResponseLen: len(services.AllNotifiers()),
BeforeTest: SetTestENV,
SecureRoute: true,
}, {
Name: "Statping Mobile Notifier",
URL: "/api/notifier/mobile",
Name: "Statping Slack Notifier",
URL: "/api/notifier/slack",
Method: "GET",
ExpectedStatus: 200,
BeforeTest: SetTestENV,
SecureRoute: true,
}, {
Name: "Statping Update Notifier",
URL: "/api/notifier/mobile",
URL: "/api/notifier/slack",
Method: "POST",
Body: `{
"method": "mobile",
"var1": "ExponentPushToken[ToBadIWillError123456]",
"method": "slack",
"host": "https://slack.api/example/12345",
"enabled": true,
"limits": 55
}`,
@ -40,11 +42,11 @@ func TestApiNotifiersRoutes(t *testing.T) {
BeforeTest: SetTestENV,
SecureRoute: true,
}, {
Name: "Statping Mobile Notifier",
URL: "/api/notifier/mobile",
Name: "Statping Slack Notifier",
URL: "/api/notifier/slack",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"method":"mobile"`, `"var1":"ExponentPushToken[ToBadIWillError123456]"`, `"enabled":true`, `"limits":55`},
ExpectedContains: []string{`"method":"slack"`},
BeforeTest: SetTestENV,
SecureRoute: true,
}}

View File

@ -74,14 +74,16 @@ func runCommand(app string, cmd ...string) (string, string, error) {
// OnFailure for commandLine will trigger failing service
func (u *commandLine) OnFailure(s *services.Service, f *failures.Failure) error {
msg := u.GetValue("var2")
_, _, err := runCommand(u.Host, msg)
tmpl := ReplaceVars(msg, s, f)
_, _, err := runCommand(u.Host, tmpl)
return err
}
// OnSuccess for commandLine will trigger successful service
func (u *commandLine) OnSuccess(s *services.Service) error {
msg := u.GetValue("var1")
_, _, err := runCommand(u.Host, msg)
tmpl := ReplaceVars(msg, s, nil)
_, _, err := runCommand(u.Host, tmpl)
return err
}

View File

@ -54,41 +54,41 @@ var Discorder = &discord{&notifications.Notification{
}
// Send will send a HTTP Post to the discord API. It accepts type: []byte
func (u *discord) sendRequest(msg string) error {
func (d *discord) sendRequest(msg string) error {
_, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
return err
}
func (u *discord) Select() *notifications.Notification {
return u.Notification
func (d *discord) Select() *notifications.Notification {
return d.Notification
}
// OnFailure will trigger failing service
func (u *discord) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue)
return u.sendRequest(msg)
func (d *discord) OnFailure(s *services.Service, f *failures.Failure) error {
msg := `{"content": "Your service '{{.Service.Name}}' is currently failing! Reason: {{.Failure.Issue}}"}`
return d.sendRequest(ReplaceVars(msg, s, f))
}
// OnSuccess will trigger successful service
func (u *discord) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf(`{"content": "Your service '%s' is currently online!"}`, s.Name)
return u.sendRequest(msg)
func (d *discord) OnSuccess(s *services.Service) error {
msg := `{"content": "Your service '{{.Service.Name}}' is currently online!"}`
return d.sendRequest(ReplaceVars(msg, s, nil))
}
// OnSave triggers when this notifier has been saved
func (u *discord) OnTest() error {
func (d *discord) OnTest() 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
}
var d discordTestJson
err = json.Unmarshal(contents, &d)
var dtt discordTestJson
err = json.Unmarshal(contents, &dtt)
if err != nil {
return outError
}
if d.Code == 0 {
if dtt.Code == 0 {
return outError
}
fmt.Println("discord: ", string(contents))

View File

@ -76,23 +76,23 @@ const (
<tr>
<td class="content-cell" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px; word-break: break-word;">
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">
{{ .Name }} is {{ if .Online }}Online{{else}}Offline{{end}}!
{{ .Service.Name }} is {{ if .Service.Online }}Online{{else}}Offline{{end}}!
</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
{{ if .Online }}
Your Statping service <a target="_blank" href="{{.Domain}}">{{.Name}}</a> is back online. This service has been triggered with a HTTP status code of '{{.LastStatusCode}}' and is currently online based on your requirements. Your service was reported online at {{.CreatedAt}}. </p>
{{ if .Service.Online }}
Your Statping service <a target="_blank" href="{{.Service.Domain}}">{{.Service.Name}}</a> is back online. This service has been triggered with a HTTP status code of '{{.Service.LastStatusCode}}' and is currently online based on your requirements. Your service was reported online at {{.Service.CreatedAt}}. </p>
{{ else }}
Your Statping service <a target="_blank" href="{{.Domain}}">{{.Name}}</a> has been triggered with a HTTP status code of '{{.LastStatusCode}}' and is currently offline based on your requirements. This failure was created on {{.CreatedAt}}. </p>
Your Statping service <a target="_blank" href="{{.Service.Domain}}">{{.Service.Name}}</a> has been triggered with a HTTP status code of '{{.Service.LastStatusCode}}' and is currently offline based on your requirements. This failure was created on {{.Service.CreatedAt}}. </p>
{{ end }}
{{if .LastResponse }}
{{if .Service.LastResponse }}
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">
Last Response</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
{{ .LastResponse }} </p> {{end}}
{{ .Service.LastResponse }} </p> {{end}}
<table class="body-sub" style="border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin-top: 25px; padding-top: 25px;">
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;"> <a href="/service/{{.Id}}" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">View Service</a> </td>
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;"> <a href="/service/{{.Service.Id}}" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">View Service</a> </td>
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;"> <a href="/dashboard" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">Statping Dashboard</a> </td>
</table>
</td>
@ -178,32 +178,32 @@ type emailOutgoing struct {
}
// OnFailure will trigger failing service
func (u *emailer) OnFailure(s *services.Service, f *failures.Failure) error {
func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) error {
email := &emailOutgoing{
To: u.Var2,
To: e.Var2,
Subject: fmt.Sprintf("Service %v is Failing", s.Name),
Template: mainEmailTemplate,
Data: interface{}(s),
From: u.Var1,
From: e.Var1,
}
return u.dialSend(email)
return e.dialSend(email)
}
// OnSuccess will trigger successful service
func (u *emailer) OnSuccess(s *services.Service) error {
func (e *emailer) OnSuccess(s *services.Service) error {
msg := s.DownText
email := &emailOutgoing{
To: u.Var2,
To: e.Var2,
Subject: msg,
Template: mainEmailTemplate,
Data: interface{}(s),
From: u.Var1,
From: e.Var1,
}
return u.dialSend(email)
return e.dialSend(email)
}
// OnTest triggers when this notifier has been saved
func (u *emailer) OnTest() error {
func (e *emailer) OnTest() error {
testService := &services.Service{
Id: 1,
Name: "Example Service",
@ -219,21 +219,21 @@ func (u *emailer) OnTest() error {
CreatedAt: utils.Now().Add(-24 * time.Hour),
}
email := &emailOutgoing{
To: u.Var2,
To: e.Var2,
Subject: fmt.Sprintf("Service %v is Back Online", testService.Name),
Template: mainEmailTemplate,
Data: testService,
From: u.Var1,
From: e.Var1,
}
return u.dialSend(email)
return e.dialSend(email)
}
func (u *emailer) dialSend(email *emailOutgoing) error {
mailer = mail.NewDialer(u.Host, u.Port, u.Username, u.Password)
func (e *emailer) dialSend(email *emailOutgoing) error {
mailer = mail.NewDialer(e.Host, e.Port, e.Username, e.Password)
emailSource(email)
m := mail.NewMessage()
// if email setting TLS is Disabled
if u.ApiKey == "true" {
if e.ApiKey == "true" {
mailer.SSL = false
} else {
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}

View File

@ -58,28 +58,28 @@ 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 (u *lineNotifier) sendMessage(message string) error {
func (l *lineNotifier) sendMessage(message string) error {
v := url.Values{}
v.Set("message", message)
headers := []string{fmt.Sprintf("Authorization=Bearer %v", u.ApiSecret)}
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
}
// OnFailure will trigger failing service
func (u *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) error {
func (l *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
return u.sendMessage(msg)
return l.sendMessage(msg)
}
// OnSuccess will trigger successful service
func (u *lineNotifier) OnSuccess(s *services.Service) error {
func (l *lineNotifier) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf("Service %s is online!", s.Name)
return u.sendMessage(msg)
return l.sendMessage(msg)
}
// OnTest triggers when this notifier has been saved
func (u *lineNotifier) OnTest() error {
func (l *lineNotifier) OnTest() error {
msg := fmt.Sprintf("Testing if Line Notifier is working!")
return u.sendMessage(msg)
return l.sendMessage(msg)
}

View File

@ -88,7 +88,7 @@ func dataJson(s *services.Service, f *failures.Failure) map[string]interface{} {
}
// OnFailure will trigger failing service
func (u *mobilePush) OnFailure(s *services.Service, f *failures.Failure) error {
func (m *mobilePush) OnFailure(s *services.Service, f *failures.Failure) error {
data := dataJson(s, f)
msg := &pushArray{
Message: fmt.Sprintf("Your service '%v' is currently failing! Reason: %v", s.Name, f.Issue),
@ -96,11 +96,11 @@ func (u *mobilePush) OnFailure(s *services.Service, f *failures.Failure) error {
Topic: mobileIdentifier,
Data: data,
}
return u.Send(msg)
return m.Send(msg)
}
// OnSuccess will trigger successful service
func (u *mobilePush) OnSuccess(s *services.Service) error {
func (m *mobilePush) OnSuccess(s *services.Service) error {
data := dataJson(s, nil)
msg := &pushArray{
Message: "Service is Online!",
@ -108,17 +108,17 @@ func (u *mobilePush) OnSuccess(s *services.Service) error {
Topic: mobileIdentifier,
Data: data,
}
return u.Send(msg)
return m.Send(msg)
}
// OnTest triggers when this notifier has been saved
func (u *mobilePush) OnTest() error {
func (m *mobilePush) OnTest() error {
msg := &pushArray{
Message: "Testing the Mobile Notifier",
Title: "Testing Notifications",
Topic: mobileIdentifier,
Tokens: []string{u.Var1},
Platform: utils.ToInt(u.Var2),
Tokens: []string{m.Var1},
Platform: utils.ToInt(m.Var2),
}
body, err := pushRequest(msg)
if err != nil {
@ -138,9 +138,9 @@ func (u *mobilePush) OnTest() error {
}
// Send will send message to Statping push notifications endpoint
func (u *mobilePush) Send(pushMessage *pushArray) error {
pushMessage.Tokens = []string{u.Var1}
pushMessage.Platform = utils.ToInt(u.Var2)
func (m *mobilePush) Send(pushMessage *pushArray) error {
pushMessage.Tokens = []string{m.Var1}
pushMessage.Platform = utils.ToInt(m.Var2)
_, err := pushRequest(pushMessage)
if err != nil {
return err

View File

@ -1,8 +1,12 @@
package notifiers
import (
"fmt"
"github.com/prometheus/common/log"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/null"
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"time"
)
func InitNotifiers() {
@ -23,7 +27,63 @@ func Add(notifs ...services.ServiceNotifier) {
for _, n := range notifs {
services.AddNotifier(n)
if err := n.Select().Create(); err != nil {
fmt.Println(err)
log.Error(err)
}
}
}
func ReplaceVars(input string, s *services.Service, f *failures.Failure) string {
input = utils.ReplaceTemplate(input, s)
if f != nil {
input = utils.ReplaceTemplate(input, f)
}
return input
}
var exampleService = &services.Service{
Id: 1,
Name: "Statping",
Domain: "https://statping.com",
Expected: null.NewNullString("a better response"),
ExpectedStatus: 200,
Interval: 60,
Type: "http",
Method: "get",
Timeout: 10,
Order: 2,
VerifySSL: null.NewNullBool(true),
Public: null.NewNullBool(true),
GroupId: 0,
Permalink: null.NewNullString("statping"),
Online: true,
Latency: 324399,
PingTime: 18399,
Online24Hours: 99.2,
Online7Days: 99.8,
AvgResponse: 300233,
FailuresLast24Hours: 4,
Checkpoint: utils.Now().Add(-10 * time.Minute),
SleepDuration: 55,
LastResponse: "returning from a response",
AllowNotifications: null.NewNullBool(true),
UserNotified: false,
UpdateNotify: null.NewNullBool(true),
SuccessNotified: false,
LastStatusCode: 200,
LastLookupTime: 5233,
LastLatency: 270233,
LastCheck: utils.Now().Add(-15 * time.Second),
LastOnline: utils.Now().Add(-15 * time.Second),
LastOffline: utils.Now().Add(-10 * time.Minute),
SecondsOnline: 4500,
SecondsOffline: 300,
}
var exampleFailure = &failures.Failure{
Id: 1,
Issue: "HTTP returned a 500 status code",
ErrorCode: 500,
Service: 1,
PingTime: 43203,
CreatedAt: utils.Now().Add(-10 * time.Minute),
}

View File

@ -18,14 +18,12 @@ package notifiers
import (
"bytes"
"errors"
"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"
"strings"
"text/template"
"time"
)
@ -33,9 +31,8 @@ var _ notifier.Notifier = (*slack)(nil)
const (
slackMethod = "slack"
failingTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> has just received a Failure notification based on your expected results. {{.Service.Name}} responded with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected Status Code", "value": "{{.Service.ExpectedStatus}}", "short": true }, { "title": "Received Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ,{ "title": "Error Message", "value": "{{.Issue}}", "short": false } ], "color": "#FF0000", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
failingTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> has just received a Failure notification based on your expected results. {{.Service.Name}} responded with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected Status Code", "value": "{{.Service.ExpectedStatus}}", "short": true }, { "title": "Received Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ,{ "title": "Error Message", "value": "{{.Failure.Issue}}", "short": false } ], "color": "#FF0000", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
successTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> is now back online and meets your expected responses.", "color": "#00FF00", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
slackText = `{"text":"{{.}}"}`
)
type slack struct {
@ -66,58 +63,36 @@ var slacker = &slack{&notifications.Notification{
}}},
}
func parseSlackMessage(id int64, temp string, data interface{}) string {
buf := new(bytes.Buffer)
slackTemp, _ := template.New("slack").Parse(temp)
err := slackTemp.Execute(buf, data)
if err != nil {
return err.Error()
}
return buf.String()
}
type slackMessage struct {
Service *services.Service
Template string
Time int64
Issue string
}
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string
func (u *slack) sendSlack(msg string) error {
contents, resp, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
func (s *slack) sendSlack(msg string) error {
_, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println("CONTENTS: ", string(contents))
return err
return nil
}
func (u *slack) OnTest() error {
contents, resp, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second), true)
func (s *slack) OnTest() 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
}
defer resp.Body.Close()
if string(contents) != "ok" {
return errors.New("the slack response was incorrect, check the URL")
}
return err
return nil
}
// OnFailure will trigger failing service
func (u *slack) OnFailure(s *services.Service, f *failures.Failure) error {
message := slackMessage{
Service: s,
Template: failingTemplate,
Time: utils.Now().Unix(),
}
msg := parseSlackMessage(s.Id, failingTemplate, message)
return u.sendSlack(msg)
func (s *slack) OnFailure(srv *services.Service, f *failures.Failure) error {
msg := ReplaceVars(failingTemplate, srv, f)
return s.sendSlack(msg)
}
// OnSuccess will trigger successful service
func (u *slack) OnSuccess(s *services.Service) error {
message := slackMessage{
Service: s,
Template: successTemplate,
Time: utils.Now().Unix(),
}
msg := parseSlackMessage(s.Id, successTemplate, message)
return u.sendSlack(msg)
func (s *slack) OnSuccess(srv *services.Service) error {
msg := ReplaceVars(successTemplate, srv, nil)
return s.sendSlack(msg)
}

View File

@ -66,11 +66,11 @@ var Telegram = &telegram{&notifications.Notification{
}
// Send will send a HTTP Post to the Telegram API. It accepts type: string
func (u *telegram) sendMessage(message string) error {
apiEndpoint := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", u.ApiSecret)
func (t *telegram) sendMessage(message string) error {
apiEndpoint := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", t.ApiSecret)
v := url.Values{}
v.Set("chat_id", u.Var1)
v.Set("chat_id", t.Var1)
v.Set("text", message)
rb := *strings.NewReader(v.Encode())
@ -86,21 +86,21 @@ func (u *telegram) sendMessage(message string) error {
}
// OnFailure will trigger failing service
func (u *telegram) OnFailure(s *services.Service, f *failures.Failure) error {
func (t *telegram) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
return u.sendMessage(msg)
return t.sendMessage(msg)
}
// OnSuccess will trigger successful service
func (u *telegram) OnSuccess(s *services.Service) error {
func (t *telegram) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name)
return u.sendMessage(msg)
return t.sendMessage(msg)
}
// OnTest will test the Twilio SMS messaging
func (u *telegram) OnTest() error {
func (t *telegram) OnTest() error {
msg := fmt.Sprintf("Testing the Twilio SMS Notifier on your Statping server")
return u.sendMessage(msg)
return t.sendMessage(msg)
}
func telegramSuccess(res []byte) (bool, telegramResponse) {

View File

@ -76,12 +76,12 @@ var Twilio = &twilio{&notifications.Notification{
}
// Send will send a HTTP Post to the Twilio SMS API. It accepts type: string
func (u *twilio) sendMessage(message string) error {
twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", u.GetValue("api_key"))
func (t *twilio) sendMessage(message string) error {
twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", t.GetValue("api_key"))
v := url.Values{}
v.Set("To", "+"+u.Var1)
v.Set("From", "+"+u.Var2)
v.Set("To", "+"+t.Var1)
v.Set("From", "+"+t.Var2)
v.Set("Body", message)
rb := *strings.NewReader(v.Encode())
@ -96,21 +96,21 @@ func (u *twilio) sendMessage(message string) error {
}
// OnFailure will trigger failing service
func (u *twilio) OnFailure(s *services.Service, f *failures.Failure) error {
func (t *twilio) OnFailure(s *services.Service, f *failures.Failure) error {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
return u.sendMessage(msg)
return t.sendMessage(msg)
}
// OnSuccess will trigger successful service
func (u *twilio) OnSuccess(s *services.Service) error {
func (t *twilio) OnSuccess(s *services.Service) error {
msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name)
return u.sendMessage(msg)
return t.sendMessage(msg)
}
// OnTest will test the Twilio SMS messaging
func (u *twilio) OnTest() error {
func (t *twilio) OnTest() error {
msg := fmt.Sprintf("Testing the Twilio SMS Notifier")
return u.sendMessage(msg)
return t.sendMessage(msg)
}
func twilioSuccess(res []byte) (bool, twilioResponse) {

View File

@ -65,8 +65,8 @@ var Webhook = &webhooker{&notifications.Notification{
}, {
Type: "textarea",
Title: "HTTP Body",
Placeholder: `{"service_id": "%s.Id", "service_name": "%s.Name"}`,
SmallText: "Optional HTTP body for a POST request. You can insert variables into your body request.<br>%service.Id, %service.Name, %service.Online<br>%failure.Issue",
Placeholder: `{"service_id": {{.Service.Id}}", "service_name": "{{.Service.Name}"}`,
SmallText: "Optional HTTP body for a POST request. You can insert variables into your body request.<br>{{.Service.Id}}, {{.Service.Name}}, {{.Service.Online}}<br>{{.Failure.Issue}}",
DbField: "Var2",
}, {
Type: "text",
@ -96,12 +96,6 @@ func (w *webhooker) Select() *notifications.Notification {
return w.Notification
}
func replaceBodyText(body string, s *services.Service, f *failures.Failure) string {
body = utils.ConvertInterface(body, s)
body = utils.ConvertInterface(body, f)
return body
}
func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
utils.Log.Infoln(fmt.Sprintf("sending body: '%v' to %v as a %v request", body, w.Host, w.Var1))
client := new(http.Client)
@ -130,11 +124,12 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
if err != nil {
return nil, err
}
return resp, err
}
func (w *webhooker) OnTest() error {
body := replaceBodyText(w.Var2, nil, nil)
body := ReplaceVars(w.Var2, exampleService, exampleFailure)
resp, err := w.sendHttpWebhook(body)
if err != nil {
return err
@ -147,7 +142,7 @@ func (w *webhooker) OnTest() error {
// OnFailure will trigger failing service
func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) error {
msg := replaceBodyText(w.Var2, s, f)
msg := ReplaceVars(w.Var2, s, f)
resp, err := w.sendHttpWebhook(msg)
defer resp.Body.Close()
return err
@ -155,7 +150,7 @@ func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) error {
// OnSuccess will trigger successful service
func (w *webhooker) OnSuccess(s *services.Service) error {
msg := replaceBodyText(w.Var2, s, nil)
msg := ReplaceVars(w.Var2, s, nil)
resp, err := w.sendHttpWebhook(msg)
defer resp.Body.Close()
return err

View File

@ -76,7 +76,11 @@ func Connect(configs *DbConfig, retry bool) error {
maxIdleConn := utils.Getenv("MAX_IDLE_CONN", 5)
maxLifeConn := utils.Getenv("MAX_LIFE_CONN", 2*time.Minute)
dbSession.DB().SetMaxOpenConns(maxOpenConn.(int))
if configs.DbConn == "sqlite3" {
dbSession.DB().SetMaxOpenConns(2)
} else {
dbSession.DB().SetMaxOpenConns(maxOpenConn.(int))
}
dbSession.DB().SetMaxIdleConns(maxIdleConn.(int))
dbSession.DB().SetConnMaxLifetime(maxLifeConn.(time.Duration))
@ -84,7 +88,7 @@ func Connect(configs *DbConfig, retry bool) error {
if utils.VerboseMode >= 4 {
dbSession.LogMode(true).Debug().SetLogger(gorm.Logger{log})
}
log.Infoln(fmt.Sprintf("Database %v connection was successful.", configs.DbConn))
log.Infoln(fmt.Sprintf("Database %s connection was successful.", configs.DbConn))
}
configs.Db = dbSession

View File

@ -35,8 +35,6 @@ func Select() (*Core, error) {
}
func (c *Core) Create() error {
c.ApiKey = c.ApiKey
c.ApiSecret = c.ApiSecret
apiKey := utils.Getenv("API_KEY", utils.NewSHA256Hash()).(string)
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA256Hash()).(string)
@ -66,23 +64,3 @@ func (c *Core) Update() error {
func (c *Core) Delete() error {
return nil
}
func Sample() error {
apiKey := utils.Getenv("API_KEY", "samplekey")
apiSecret := utils.Getenv("API_SECRET", "samplesecret")
core := &Core{
Name: "Statping Sample Data",
Description: "This data is only used to testing",
ApiKey: apiKey.(string),
ApiSecret: apiSecret.(string),
Domain: "http://localhost:8080",
Version: "test",
CreatedAt: time.Now().UTC(),
UseCdn: null.NewNullBool(false),
Footer: null.NewNullString(""),
}
q := db.Create(core)
return q.Error()
}

View File

@ -12,12 +12,6 @@ func DB() database.Database {
return db
}
func Find(id int64) (*Failure, error) {
var failure Failure
q := db.Where("id = ?", id).Find(&failure)
return &failure, q.Error()
}
func All() []*Failure {
var failures []*Failure
db.Find(&failures)

View File

@ -1,6 +1,7 @@
package notifications
import (
"errors"
"github.com/statping/statping/database"
)
@ -15,6 +16,9 @@ func SetDB(database database.Database) {
func Find(method string) (*Notification, error) {
var notification Notification
q := db.Where("method = ?", method).Find(&notification)
if &notification == nil {
return nil, errors.New("cannot find notifier")
}
return &notification, q.Error()
}

View File

@ -83,8 +83,7 @@ func (n *Notification) CanSend() bool {
// GetValue returns the database value of a accept DbField value.
func (n *Notification) GetValue(dbField string) string {
dbField = strings.ToLower(dbField)
switch dbField {
switch strings.ToLower(dbField) {
case "host":
return n.Host
case "port":
@ -109,6 +108,36 @@ func (n *Notification) GetValue(dbField string) string {
return ""
}
// ResetQueue will clear the notifiers Queue
func (n *Notification) ResetQueue() {
n.Queue = nil
}
// start will start the go routine for the notifier queue
func (n *Notification) Start() {
n.Running = make(chan bool)
}
// close will stop the go routine for queue
func (n *Notification) Close() {
if n.IsRunning() {
close(n.Running)
}
}
// IsRunning will return true if the notifier is currently running a queue
func (n *Notification) IsRunning() bool {
if n.Running == nil {
return false
}
select {
case <-n.Running:
return false
default:
return true
}
}
// Init accepts the Notifier interface to initialize the notifier
//func Init(n Notifier) (*Notification, error) {
// if Exists(n.Select().Method) {
@ -139,33 +168,3 @@ func (n *Notification) GetValue(dbField string) string {
//
// return nil, err
//}
// ResetQueue will clear the notifiers Queue
func (n *Notification) ResetQueue() {
n.Queue = nil
}
// start will start the go routine for the notifier queue
func (n *Notification) Start() {
n.Running = make(chan bool)
}
// close will stop the go routine for queue
func (n *Notification) Close() {
if n.IsRunning() {
close(n.Running)
}
}
// IsRunning will return true if the notifier is currently running a queue
func (n *Notification) IsRunning() bool {
if n.Running == nil {
return false
}
select {
case <-n.Running:
return false
default:
return true
}
}

View File

@ -271,6 +271,7 @@ func sendSuccess(s *Service) {
//s.UpdateNotify.Bool
}
}
s.notifyAfterCount = 0
}
// recordFailure will create a new 'Failure' record in the database for a offline service
@ -307,18 +308,22 @@ func sendFailure(s *Service, f *failures.Failure) {
return
}
for _, n := range allNotifiers {
notif := n.Select()
if notif.CanSend() {
log.Infof("Sending Failure notification to: %s!", notif.Method)
if err := n.OnFailure(s, f); err != nil {
log.Errorln(err)
if s.NotifyAfter == 0 || s.notifyAfterCount > s.NotifyAfter {
for _, n := range allNotifiers {
notif := n.Select()
if notif.CanSend() {
log.Infof("Sending Failure notification to: %s!", notif.Method)
if err := n.OnFailure(s, f); err != nil {
log.Errorln(err)
}
s.UserNotified = true
s.SuccessNotified = true
//s.UpdateNotify.Bool
}
s.UserNotified = true
s.SuccessNotified = true
//s.UpdateNotify.Bool
}
}
s.notifyAfterCount++
}
// Check will run checkHttp for HTTP services and checkTcp for TCP services

View File

@ -2,11 +2,12 @@ package services
import (
"github.com/statping/statping/types/null"
"github.com/statping/statping/utils"
"time"
)
func Samples() error {
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
createdOn := utils.Now().Add(((-24 * 30) * 3) * time.Hour)
s1 := &Service{
Name: "Google",
Domain: "https://google.com",
@ -19,6 +20,7 @@ func Samples() error {
GroupId: 1,
Permalink: null.NewNullString("google"),
VerifySSL: null.NewNullBool(true),
NotifyAfter: 3,
CreatedAt: createdOn,
}
if err := s1.Create(); err != nil {
@ -36,6 +38,7 @@ func Samples() error {
Order: 2,
Permalink: null.NewNullString("statping_github"),
VerifySSL: null.NewNullBool(true),
NotifyAfter: 1,
CreatedAt: createdOn,
}
if err := s2.Create(); err != nil {
@ -54,6 +57,7 @@ func Samples() error {
Public: null.NewNullBool(true),
VerifySSL: null.NewNullBool(true),
GroupId: 2,
NotifyAfter: 2,
CreatedAt: createdOn,
}
if err := s3.Create(); err != nil {
@ -74,6 +78,7 @@ func Samples() error {
Public: null.NewNullBool(true),
VerifySSL: null.NewNullBool(true),
GroupId: 2,
NotifyAfter: 3,
CreatedAt: createdOn,
}
if err := s4.Create(); err != nil {

View File

@ -51,6 +51,8 @@ type Service struct {
Checkpoint time.Time `gorm:"-" json:"-"`
SleepDuration time.Duration `gorm:"-" json:"-"`
LastResponse string `gorm:"-" json:"-"`
NotifyAfter int64 `gorm:"column:notify_after" json:"notify_after" scope:"user,admin"`
notifyAfterCount int64 `gorm:"-" json:"-"`
AllowNotifications null.NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications" scope:"user,admin"`
UserNotified bool `gorm:"-" json:"-"` // True if the User was already notified about a Downtime
UpdateNotify null.NullBool `gorm:"default:true;column:notify_all_changes" json:"notify_all_changes" scope:"user,admin"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool`

68
utils/file.go Normal file
View File

@ -0,0 +1,68 @@
package utils
import (
"io/ioutil"
"os"
)
// DeleteDirectory will attempt to delete a directory and all contents inside
// DeleteDirectory("assets")
func DeleteDirectory(directory string) error {
Log.Debugln("removing directory: " + directory)
return os.RemoveAll(directory)
}
// CreateDirectory will attempt to create a directory
// CreateDirectory("assets")
func CreateDirectory(directory string) error {
Log.Debugln("creating directory: " + directory)
if err := os.Mkdir(directory, os.ModePerm); err != os.ErrExist {
return err
}
return nil
}
// FolderExists will return true if the folder exists
func FolderExists(folder string) bool {
if _, err := os.Stat(folder); os.IsExist(err) {
return true
}
return false
}
// FileExists returns true if a file exists
// exists := FileExists("assets/css/base.css")
func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
Log.Debugf("file exist: %v (%v)", name, !os.IsNotExist(err))
if os.IsNotExist(err) {
return false
}
}
return true
}
// DeleteFile will attempt to delete a file
// DeleteFile("newfile.json")
func DeleteFile(file string) error {
Log.Debugln("deleting file: " + file)
return os.Remove(file)
}
// RenameDirectory will attempt rename a directory to a new name
func RenameDirectory(fromDir string, toDir string) error {
Log.Debugln("renaming directory: " + fromDir + "to: " + toDir)
return os.Rename(fromDir, toDir)
}
// SaveFile will create a new file with data inside it
// SaveFile("newfile.json", []byte('{"data": "success"}')
func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, os.ModePerm)
return err
}
func OpenFile(filePath string) (string, error) {
data, err := ioutil.ReadFile(filePath)
return string(data), err
}

View File

@ -8,7 +8,7 @@ func init() {
var (
httpMetric *Metrics
StartTime = time.Now()
StartTime = Now()
)
type Metrics struct {

118
utils/perlin.go Normal file
View File

@ -0,0 +1,118 @@
package utils
import (
"math"
"math/rand"
)
const (
B = 0x100
N = 0x1000
BM = 0xff
)
func NewPerlin(alpha, beta float64, n int, seed int64) *Perlin {
return NewPerlinRandSource(alpha, beta, n, rand.NewSource(seed))
}
// Perlin is the noise generator
type Perlin struct {
alpha float64
beta float64
n int
p [B + B + 2]int
g3 [B + B + 2][3]float64
g2 [B + B + 2][2]float64
g1 [B + B + 2]float64
}
func NewPerlinRandSource(alpha, beta float64, n int, source rand.Source) *Perlin {
var p Perlin
var i int
p.alpha = alpha
p.beta = beta
p.n = n
r := rand.New(source)
for i = 0; i < B; i++ {
p.p[i] = i
p.g1[i] = float64((r.Int()%(B+B))-B) / B
for j := 0; j < 2; j++ {
p.g2[i][j] = float64((r.Int()%(B+B))-B) / B
}
normalize2(&p.g2[i])
}
for ; i > 0; i-- {
k := p.p[i]
j := r.Int() % B
p.p[i] = p.p[j]
p.p[j] = k
}
for i := 0; i < B+2; i++ {
p.p[B+i] = p.p[i]
p.g1[B+i] = p.g1[i]
for j := 0; j < 2; j++ {
p.g2[B+i][j] = p.g2[i][j]
}
for j := 0; j < 3; j++ {
p.g3[B+i][j] = p.g3[i][j]
}
}
return &p
}
func normalize2(v *[2]float64) {
s := math.Sqrt(v[0]*v[0] + v[1]*v[1])
v[0] = v[0] / s
v[1] = v[1] / s
}
func (p *Perlin) Noise1D(x float64) float64 {
var scale float64 = 1
var sum float64
px := x
for i := 0; i < p.n; i++ {
val := p.noise1(px)
sum += val / scale
scale *= p.alpha
px *= p.beta
}
if sum < 0 {
sum = sum * -1
}
return sum
}
func (p *Perlin) noise1(arg float64) float64 {
var vec [1]float64
vec[0] = arg
t := vec[0] + N
bx0 := int(t) & BM
bx1 := (bx0 + 1) & BM
rx0 := t - float64(int(t))
rx1 := rx0 - 1.
sx := sCurve(rx0)
u := rx0 * p.g1[p.p[bx0]]
v := rx1 * p.g1[p.p[bx1]]
return lerp(sx, u, v)
}
func sCurve(t float64) float64 {
return t * t * (3. - 2.*t)
}
func lerp(t, a, b float64) float64 {
return a + t*(b-a)
}

33
utils/replacer.go Normal file
View File

@ -0,0 +1,33 @@
package utils
import (
"bytes"
"fmt"
"text/template"
)
func ReplaceTemplate(tmpl string, data interface{}) string {
buf := new(bytes.Buffer)
var varStr string
switch fmt.Sprintf("%T", data) {
case "*services.Service":
varStr = "Service"
case "*failures.Failure":
varStr = "Failure"
default:
varStr = "Object"
}
injectVars := make(map[string]interface{})
injectVars[varStr] = data
slackTemp, err := template.New("replacement").Parse(tmpl)
if err != nil {
Log.Error(err)
return err.Error()
}
err = slackTemp.Execute(buf, injectVars)
if err != nil {
Log.Error(err)
return err.Error()
}
return buf.String()
}

View File

@ -20,12 +20,6 @@ import (
"time"
)
const (
FlatpickrTime = "2006-01-02 15:04"
FlatpickrDay = "2006-01-02"
FlatpickrReadable = "Mon, 02 Jan 2006"
)
// Timezoner returns the time.Time with the user set timezone
func Timezoner(t time.Time, zone float32) time.Time {
zoneInt := float32(3600) * zone

View File

@ -23,14 +23,10 @@ import (
"github.com/ararog/timeago"
"io"
"io/ioutil"
"math"
"math/rand"
"net"
"net/http"
"os"
"os/exec"
"reflect"
"regexp"
"strconv"
"strings"
"time"
@ -91,14 +87,6 @@ func Getenv(key string, defaultValue interface{}) interface{} {
return defaultValue
}
func SliceConvert(g []*interface{}) []interface{} {
var arr []interface{}
for _, v := range g {
arr = append(arr, v)
}
return arr
}
// ToInt converts a int to a string
func ToInt(s interface{}) int64 {
switch v := s.(type) {
@ -127,25 +115,6 @@ func ToInt(s interface{}) int64 {
}
}
// ConvertInterface will take all the keys/values from an interface and replace all %type.Key from a string
// Input: {"name": "%service.Name", "domain": "%service.Domain"}
// Output: {"name": "Google DNS", "domain": "8.8.8.8"}
func ConvertInterface(in string, obj interface{}) string {
if reflect.ValueOf(obj).IsNil() {
return in
}
s := reflect.ValueOf(obj).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
find := strings.Split(fmt.Sprintf("%s.%v", typeOfT, typeOfT.Field(i).Name), ".")
find[1] = strings.ToLower(find[1])
key := strings.Join(find[1:], ".")
in = strings.ReplaceAll(in, fmt.Sprintf("%%%v", key), fmt.Sprintf("%v", f.Interface()))
}
return in
}
// ToString converts a int to a string
func ToString(s interface{}) string {
switch v := s.(type) {
@ -177,117 +146,6 @@ func (t Timestamp) Ago() string {
return got
}
// UnderScoreString will return a string that replaces spaces and other characters to underscores
// UnderScoreString("Example String")
// // example_string
func UnderScoreString(str string) string {
// convert every letter to lower case
newStr := strings.ToLower(str)
// convert all spaces/tab to underscore
regExp := regexp.MustCompile("[[:space:][:blank:]]")
newStrByte := regExp.ReplaceAll([]byte(newStr), []byte("_"))
regExp = regexp.MustCompile("`[^a-z0-9]`i")
newStrByte = regExp.ReplaceAll(newStrByte, []byte("_"))
regExp = regexp.MustCompile("[!/']")
newStrByte = regExp.ReplaceAll(newStrByte, []byte("_"))
// and remove underscore from beginning and ending
newStr = strings.TrimPrefix(string(newStrByte), "_")
newStr = strings.TrimSuffix(newStr, "_")
return newStr
}
// FileExists returns true if a file exists
// exists := FileExists("assets/css/base.css")
func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
Log.Debugf("file exist: %v (%v)", name, !os.IsNotExist(err))
if os.IsNotExist(err) {
return false
}
}
return true
}
// DeleteFile will attempt to delete a file
// DeleteFile("newfile.json")
func DeleteFile(file string) error {
Log.Debugln("deleting file: " + file)
return os.Remove(file)
}
// RenameDirectory will attempt rename a directory to a new name
func RenameDirectory(fromDir string, toDir string) error {
Log.Debugln("renaming directory: " + fromDir + "to: " + toDir)
return os.Rename(fromDir, toDir)
}
// DeleteDirectory will attempt to delete a directory and all contents inside
// DeleteDirectory("assets")
func DeleteDirectory(directory string) error {
Log.Debugln("removing directory: " + directory)
return os.RemoveAll(directory)
}
// CreateDirectory will attempt to create a directory
// CreateDirectory("assets")
func CreateDirectory(directory string) error {
Log.Debugln("creating directory: " + directory)
if err := os.Mkdir(directory, os.ModePerm); err != os.ErrExist {
return err
}
return nil
}
// FolderExists will return true if the folder exists
func FolderExists(folder string) bool {
if _, err := os.Stat(folder); os.IsExist(err) {
return true
}
return false
}
func OpenFile(filePath string) (string, error) {
data, err := ioutil.ReadFile(filePath)
return string(data), err
}
// CopyFile will copy a file to a new directory
// CopyFile("source.jpg", "/tmp/source.jpg")
func CopyFile(src, dst string) error {
Log.Debugln(fmt.Sprintf("copying file: %v to %v", src, dst))
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Close()
}
// IsType will return true if a variable can implement an interface
func IsType(n interface{}, obj interface{}) bool {
one := reflect.TypeOf(n)
two := reflect.ValueOf(obj).Elem()
return one.Implements(two.Type())
}
// Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings
// in, out, err := Command("sass assets/scss assets/css/base.css")
func Command(name string, args ...string) (string, string, error) {
@ -362,13 +220,6 @@ func DurationReadable(d time.Duration) string {
return d.String()
}
// SaveFile will create a new file with data inside it
// SaveFile("newfile.json", []byte('{"data": "success"}')
func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, os.ModePerm)
return err
}
// HttpRequest is a global function to send a HTTP request
// // url - The URL for HTTP request
// // method - GET, POST, DELETE, PATCH
@ -446,115 +297,3 @@ func HttpRequest(url, method string, content interface{}, headers []string, body
return contents, resp, err
}
const (
B = 0x100
N = 0x1000
BM = 0xff
)
func NewPerlin(alpha, beta float64, n int, seed int64) *Perlin {
return NewPerlinRandSource(alpha, beta, n, rand.NewSource(seed))
}
// Perlin is the noise generator
type Perlin struct {
alpha float64
beta float64
n int
p [B + B + 2]int
g3 [B + B + 2][3]float64
g2 [B + B + 2][2]float64
g1 [B + B + 2]float64
}
func NewPerlinRandSource(alpha, beta float64, n int, source rand.Source) *Perlin {
var p Perlin
var i int
p.alpha = alpha
p.beta = beta
p.n = n
r := rand.New(source)
for i = 0; i < B; i++ {
p.p[i] = i
p.g1[i] = float64((r.Int()%(B+B))-B) / B
for j := 0; j < 2; j++ {
p.g2[i][j] = float64((r.Int()%(B+B))-B) / B
}
normalize2(&p.g2[i])
}
for ; i > 0; i-- {
k := p.p[i]
j := r.Int() % B
p.p[i] = p.p[j]
p.p[j] = k
}
for i := 0; i < B+2; i++ {
p.p[B+i] = p.p[i]
p.g1[B+i] = p.g1[i]
for j := 0; j < 2; j++ {
p.g2[B+i][j] = p.g2[i][j]
}
for j := 0; j < 3; j++ {
p.g3[B+i][j] = p.g3[i][j]
}
}
return &p
}
func normalize2(v *[2]float64) {
s := math.Sqrt(v[0]*v[0] + v[1]*v[1])
v[0] = v[0] / s
v[1] = v[1] / s
}
func (p *Perlin) Noise1D(x float64) float64 {
var scale float64 = 1
var sum float64
px := x
for i := 0; i < p.n; i++ {
val := p.noise1(px)
sum += val / scale
scale *= p.alpha
px *= p.beta
}
if sum < 0 {
sum = sum * -1
}
return sum
}
func (p *Perlin) noise1(arg float64) float64 {
var vec [1]float64
vec[0] = arg
t := vec[0] + N
bx0 := int(t) & BM
bx1 := (bx0 + 1) & BM
rx0 := t - float64(int(t))
rx1 := rx0 - 1.
sx := sCurve(rx0)
u := rx0 * p.g1[p.p[bx0]]
v := rx1 * p.g1[p.p[bx1]]
return lerp(sx, u, v)
}
func sCurve(t float64) float64 {
return t * t * (3. - 2.*t)
}
func lerp(t, a, b float64) float64 {
return a + t*(b-a)
}

View File

@ -26,17 +26,6 @@ import (
"time"
)
func TestConvertInterface(t *testing.T) {
type Service struct {
Name string
Domain string
}
sample := `{"name": "%service.Name", "domain": "%service.Domain"}`
input := &Service{"Test Name", "statping.com"}
out := ConvertInterface(sample, input)
assert.Equal(t, `{"name": "Test Name", "domain": "statping.com"}`, out)
}
func TestCreateLog(t *testing.T) {
err := createLog(Directory)
assert.Nil(t, err)
@ -59,6 +48,22 @@ func TestCommand(t *testing.T) {
assert.Empty(t, out)
}
func TestReplaceTemplate(t *testing.T) {
type Object struct {
Id int64
String string
Online bool
Example string
}
example := &Object{
1, "this is an example", true, "it should work",
}
result := ReplaceTemplate(`{"id": {{.Object.Id}} }`, example)
assert.Equal(t, "{\"id\": 1 }", result)
}
func TestToInt(t *testing.T) {
assert.Equal(t, int64(55), ToInt("55"))
assert.Equal(t, int64(55), ToInt(55))
@ -73,7 +78,7 @@ func TestToString(t *testing.T) {
dir, _ := time.ParseDuration("55s")
assert.Equal(t, "55s", ToString(dir))
assert.Equal(t, "true", ToString(true))
assert.Equal(t, time.Now().Format("Monday January _2, 2006 at 03:04PM"), ToString(time.Now()))
assert.Equal(t, Now().Format("Monday January _2, 2006 at 03:04PM"), ToString(Now()))
}
func ExampleToString() {
@ -146,10 +151,6 @@ func TestTimestamp_Ago(t *testing.T) {
assert.Equal(t, "Just now", now.Ago())
}
func TestUnderScoreString(t *testing.T) {
assert.Equal(t, "this_is_a_test", UnderScoreString("this is a test"))
}
func TestHashPassword(t *testing.T) {
assert.Equal(t, 60, len(HashPassword("password123")))
}

View File

@ -1 +1 @@
0.90.15
0.90.16