mirror of https://github.com/statping/statping
parent
11538c38e3
commit
f331e9bbf9
|
@ -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 ./...
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
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) {
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -54,41 +54,41 @@ var Discorder = &discord{¬ifications.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))
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -58,28 +58,28 @@ var LineNotify = &lineNotifier{¬ifications.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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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{¬ifications.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)
|
||||
defer resp.Body.Close()
|
||||
fmt.Println("CONTENTS: ", string(contents))
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -66,11 +66,11 @@ var Telegram = &telegram{¬ifications.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) {
|
||||
|
|
|
@ -76,12 +76,12 @@ var Twilio = &twilio{¬ifications.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) {
|
||||
|
|
|
@ -65,8 +65,8 @@ var Webhook = &webhooker{¬ifications.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
|
||||
|
|
|
@ -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)
|
||||
|
||||
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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(¬ification)
|
||||
if ¬ification == nil {
|
||||
return nil, errors.New("cannot find notifier")
|
||||
}
|
||||
return ¬ification, q.Error()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,6 +308,7 @@ func sendFailure(s *Service, f *failures.Failure) {
|
|||
return
|
||||
}
|
||||
|
||||
if s.NotifyAfter == 0 || s.notifyAfterCount > s.NotifyAfter {
|
||||
for _, n := range allNotifiers {
|
||||
notif := n.Select()
|
||||
if notif.CanSend() {
|
||||
|
@ -319,6 +321,9 @@ func sendFailure(s *Service, f *failures.Failure) {
|
|||
//s.UpdateNotify.Bool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.notifyAfterCount++
|
||||
}
|
||||
|
||||
// Check will run checkHttp for HTTP services and checkTcp for TCP services
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -8,7 +8,7 @@ func init() {
|
|||
|
||||
var (
|
||||
httpMetric *Metrics
|
||||
StartTime = time.Now()
|
||||
StartTime = Now()
|
||||
)
|
||||
|
||||
type Metrics struct {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
|
261
utils/utils.go
261
utils/utils.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")))
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.90.15
|
||||
0.90.16
|
Loading…
Reference in New Issue