Merge pull request #687 from statping/dev

Version to 0.90.55
pull/702/head^2
Hunter Long 2020-06-21 00:41:36 -07:00 committed by GitHub
commit 3fcf865269
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1388 additions and 242 deletions

View File

@ -7,7 +7,7 @@ about: If you're having an issue or see an error
### Describe the bug
Try to explain what issue your'e having in detail. You can copy and paste the issue from the log file in your root directory `/logs/statping.log`.
You can set the environment variable `ALLOW_REPORTS` to `true` to allow errors to be sent to our error reporting server.
You can set the environment variable `ALLOW_REPORTS` to `true` to allow errors to be sent to our error reporting server. It's super helpful.
### To Reproduce
Steps to reproduce the behavior:

View File

@ -7,7 +7,7 @@ about: If you're having an issue or see an error
### Describe the bug
A clear and concise description of what the bug is.
You can set the environment variable `ALLOW_REPORTS` to `true` to allow errors to be sent to our error reporting server.
You can set the environment variable `ALLOW_REPORTS` to `true` to allow errors to be sent to our error reporting server. It's super helpful.
### To Reproduce
Steps to reproduce the behavior:
@ -20,6 +20,6 @@ Steps to reproduce the behavior:
A clear and concise description of what you expected to happen.
### Screenshots or Logs
If applicable, add screenshots to help explain your problem. If you can, provide any logs from the latest `logs/statping.log` file.
If applicable, add screenshots to help explain your problem. If you can, provide any logs from the latest `logs/statping.log` file.
[![Slack](https://slack.statping.com/badge.svg)](https://slack.statping.com/) [![GitHub release](https://img.shields.io/github/release/hunterlong/statup.svg)](https://github.com/statping/statping/releases/latest) [![Build Status](https://travis-ci.com/hunterlong/statup.svg?branch=master)](https://travis-ci.com/hunterlong/statup)

View File

@ -1,13 +1,10 @@
name: Push Request Testing
name: Dev Release
on:
push:
branches:
- '*' # matches every branch
- '*/*' # matches every branch containing a single '/'
- '!master' # excludes master
pull_request:
branches:
- master
- dev
paths-ignore:
- '**.md'
jobs:
compile:
@ -51,7 +48,7 @@ jobs:
name: static-rice-box
path: ./source
pr-test:
test:
needs: compile
runs-on: ubuntu-latest
@ -85,9 +82,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install Global Dependencies
run: |
go get gotest.tools/gotestsum
npm install -g yarn sass newman cross-env wait-on @sentry/cli
run: npm install -g yarn sass newman cross-env wait-on @sentry/cli
- name: Setting ENV's
run: |
@ -112,7 +107,8 @@ jobs:
- name: Go Tests
run: |
SASS=`which sass` gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./...
go get gotest.tools/gotestsum
gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./...
env:
VERSION: ${{ env.VERSION }}
DB_CONN: sqlite3
@ -120,9 +116,35 @@ jobs:
API_SECRET: demopassword123
DISABLE_LOGS: false
ALLOW_REPORTS: true
PUSH_REQUEST: true
COVERALLS: ${{ secrets.COVERALLS }}
DISCORD_URL: ${{ secrets.DISCORD_URL }}
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
EMAIL_USER: ${{ secrets.EMAIL_USER }}
EMAIL_PASS: ${{ secrets.EMAIL_PASS }}
EMAIL_OUTGOING: ${{ secrets.EMAIL_OUTGOING }}
EMAIL_SEND_TO: ${{ secrets.EMAIL_SEND_TO }}
EMAIL_PORT: ${{ secrets.EMAIL_PORT }}
MOBILE_ID: ${{ secrets.MOBILE_ID }}
MOBILE_NUMBER: ${{ secrets.MOBILE_NUMBER }}
PUSHOVER_TOKEN: ${{ secrets.PUSHOVER_TOKEN }}
PUSHOVER_API: ${{ secrets.PUSHOVER_API }}
SLACK_URL: ${{ secrets.SLACK_URL }}
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
TELEGRAM_CHANNEL: ${{ secrets.TELEGRAM_CHANNEL }}
TWILIO_SID: ${{ secrets.TWILIO_SID }}
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
TWILIO_TO: ${{ secrets.TWILIO_TO }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
pr-test-postman:
- name: Coveralls Testing Coverage
run: |
go get github.com/mattn/goveralls
goveralls -coverprofile=coverage.out -repotoken $COVERALLS
env:
COVERALLS: ${{ secrets.COVERALLS }}
test-postman-sqlite:
needs: compile
runs-on: ubuntu-latest
steps:
@ -154,13 +176,159 @@ jobs:
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8080 > /dev/null &
sleep 3
API_SECRET=demosecret123 statping --port=8585 > /dev/null &
sleep 5
- name: Postman Tests
- name: Postman SQLite Tests
uses: matt-ball/newman-action@master
with:
apiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json
environment: ./dev/postman_environment_sqlite.json
timeoutRequest: 15000
delayRequest: 500
environment: ./dev/postman_env_sqlite.json
timeoutRequest: 30000
delayRequest: 600
test-postman-mysql:
needs: compile
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- name: Setting ENV's
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Download Compiled Frontend (rice-box.go)
uses: actions/download-artifact@v1
with:
name: static-rice-box
path: ./source
- name: Install Statping
env:
VERSION: ${{ env.VERSION }}
run: |
make build
chmod +x statping
mv statping $(go env GOPATH)/bin/
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8585 > /dev/null &
sleep 5
- name: Postman MySQL Tests
uses: matt-ball/newman-action@master
with:
apiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json
environment: ./dev/postman_env_mysql.json
timeoutRequest: 30000
delayRequest: 600
test-postman-postgres:
needs: compile
runs-on: ubuntu-latest
services:
postgres:
image: postgres:10.8
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: password123
POSTGRES_DB: statping
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- name: Setting ENV's
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Download Compiled Frontend (rice-box.go)
uses: actions/download-artifact@v1
with:
name: static-rice-box
path: ./source
- name: Install Statping
env:
VERSION: ${{ env.VERSION }}
run: |
make build
chmod +x statping
mv statping $(go env GOPATH)/bin/
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8585 > /dev/null &
sleep 5
- name: Postman Postgres Tests
uses: matt-ball/newman-action@master
with:
apiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json
environment: ./dev/postman_env_postgres.json
timeoutRequest: 30000
delayRequest: 600
docker-release:
needs: [test, test-postman-sqlite, test-postman-mysql, test-postman-postgres]
runs-on: ubuntu-latest
steps:
- name: Checkout Statping Repo
uses: actions/checkout@v2
- name: Setting ENV's
run: echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Base Docker Image
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: statping/statping
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
dockerfile: Dockerfile.base
tags: "base"
- name: Latest/Version Docker Image
uses: elgohr/Publish-Docker-Github-Action@master
env:
VERSION: ${{ env.VERSION }}
ARCH: amd64
DOCKER_CLI_EXPERIMENTAL: enabled
with:
name: statping/statping
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
dockerfile: Dockerfile
tags: "dev"
buildargs: VERSION,ARCH

View File

@ -135,6 +135,7 @@ jobs:
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
TWILIO_TO: ${{ secrets.TWILIO_TO }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
- name: Coveralls Testing Coverage
run: |
@ -388,7 +389,7 @@ jobs:
with:
tag_name: v${{ env.VERSION }}
draft: false
prerelease: false
prerelease: true
files: |
builds/statping-linux-386.tar.gz
builds/statping-linux-amd64.tar.gz

167
.github/workflows/pr.yml vendored Normal file
View File

@ -0,0 +1,167 @@
name: Push Request Testing
on:
push:
branches:
- '*' # matches every branch
- '*/*' # matches every branch containing a single '/'
- '!master' # excludes master
- '!dev' # excludes dev
pull_request:
branches:
- master
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- uses: actions/setup-node@v1
with:
node-version: '10.x'
- uses: actions/checkout@v2
- name: Add GOBIN to PATH
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Install Global Dependencies
run: npm install -g yarn sass cross-env
- name: Download Frontend Dependencies
if: steps.nodecache.outputs.cache-hit != 'true'
working-directory: ./frontend
run: yarn
- name: Download Go mods
if: steps.golangcache.outputs.cache-hit != 'true'
run: |
go mod download
go mod verify
make test-deps
- name: Build Frontend Statping
run: make clean compile
- name: Upload Compiled Frontend (rice-box.go)
uses: actions/upload-artifact@v1
with:
name: static-rice-box
path: ./source
pr-test:
needs: compile
runs-on: ubuntu-latest
services:
postgres:
image: postgres:10.8
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: password123
POSTGRES_DB: statping
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- uses: actions/setup-node@v1
with:
node-version: '10.x'
- uses: actions/checkout@v2
- name: Install Global Dependencies
run: |
go get gotest.tools/gotestsum
npm install -g yarn sass newman cross-env wait-on @sentry/cli
- name: Setting ENV's
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Download Compiled Frontend (rice-box.go)
uses: actions/download-artifact@v1
with:
name: static-rice-box
path: ./source
- name: Install Statping
env:
VERSION: ${{ env.VERSION }}
run: |
make build certs
chmod +x statping
mv statping $(go env GOPATH)/bin/
- name: Go Tests
run: |
SASS=`which sass` gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./...
env:
VERSION: ${{ env.VERSION }}
DB_CONN: sqlite3
STATPING_DIR: ${{ github.workspace }}
API_SECRET: demopassword123
DISABLE_LOGS: false
ALLOW_REPORTS: true
PUSH_REQUEST: true
pr-test-postman:
needs: compile
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- name: Setting ENV's
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Download Compiled Frontend (rice-box.go)
uses: actions/download-artifact@v1
with:
name: static-rice-box
path: ./source
- name: Install Statping
env:
VERSION: ${{ env.VERSION }}
run: |
make build
chmod +x statping
mv statping $(go env GOPATH)/bin/
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8080 > /dev/null &
sleep 3
- name: Postman Tests
uses: matt-ball/newman-action@master
with:
collection: ./dev/postman.json
environment: ./dev/postman_environment_sqlite.json
timeoutRequest: 15000
delayRequest: 500

View File

@ -1,3 +1,10 @@
# 0.90.55 (06-18-2020)
- Added 404 page
- Modified Statping's PR process, dev -> master
- Fixed Discord notifier
- Modified email template for SMTP emails
- Added OnSave() method for all notifiers
# 0.90.54 (06-17-2020)
- Fixed Slack Notifier's failure/success data saving issue
- Added additional i18n Languages (help needed!)

View File

@ -9,7 +9,7 @@
# Statping - Status Page & Monitoring Server
An easy to use Status Page for your websites and applications. Statping will automatically fetch the application and render a beautiful status page with tons of features for you to build an even better status page. This Status Page generator allows you to use MySQL, Postgres, or SQLite on multiple operating systems.
[![Latest](https://github.com/statping/statping/workflows/Master%20Release/badge.svg)](https://github.com/statping/statping/actions) [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/statping/statping) [![Slack](https://slack.statping.com/badge.svg)](https://slack.statping.com) [![](https://images.microbadger.com/badges/image/statping/statping.svg)](https://microbadger.com/images/statping/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/statping/statping.svg)](https://hub.docker.com/r/statping/statping/builds/)
![Master Release](https://github.com/statping/statping/workflows/Master%20Release/badge.svg?branch=master) [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/statping/statping) [![Slack](https://slack.statping.com/badge.svg)](https://slack.statping.com) [![](https://images.microbadger.com/badges/image/statping/statping.svg)](https://microbadger.com/images/statping/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/statping/statping.svg)](https://hub.docker.com/r/statping/statping/builds/)
<br><br>
<img align="left" width="320" height="235" src="https://img.cjx.io/statupsiterun.gif">
@ -156,8 +156,9 @@ aws ec2 run-instances \
```
## Contributing
Statping accepts Push Requests! Feel free to add your own features and notifiers. You probably want to checkout the [Notifier Wiki](https://github.com/statping/statping/wiki/Notifiers) to get a better understanding on how to create your own notification methods for failing/successful services. Testing on Statping will test each function on MySQL, Postgres, and SQLite. I recommend you run a MySQL and a Postgres Docker image for testing.
Statping accepts Push Requests to the `dev` branch! Feel free to add your own features and notifiers. You probably want to checkout the [Notifier Wiki](https://github.com/statping/statping/wiki/Notifiers) to get a better understanding on how to create your own notification methods for failing/successful services. Testing on Statping will test each function on MySQL, Postgres, and SQLite. I recommend running MySQL and Postgres Docker containers for testing. You can find multiple docker-compose files in the dev directory.
![Dev Release](https://github.com/statping/statping/workflows/Dev%20Release/badge.svg?branch=dev)
[![Go Report Card](https://goreportcard.com/badge/github.com/statping/statping)](https://goreportcard.com/report/github.com/statping/statping)
[![Build Status](https://travis-ci.com/statping/statping.svg?branch=master)](https://travis-ci.com/statping/statping) [![Cypress.io tests](https://img.shields.io/badge/cypress.io-tests-green.svg?style=flat-square)](https://dashboard.cypress.io/#/projects/bi8mhr/runs)
[![Docker Pulls](https://img.shields.io/docker/pulls/statping/statping.svg)](https://hub.docker.com/r/statping/statping/builds/) [![Godoc](https://godoc.org/github.com/statping/statping?status.svg)](https://godoc.org/github.com/statping/statping)[![Coverage Status](https://coveralls.io/repos/github/statping/statping/badge.svg?branch=master)](https://coveralls.io/github/statping/statping?branch=master)

58
dev/postman.json vendored

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@
{{$t('dashboard.wrong_login')}}
</div>
<button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="disabled || loading">
{{loading ? $t('dashboard.loading') : $t('dashboard.sign_in')}}
<font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/>{{loading ? $t('dashboard.loading') : $t('dashboard.sign_in')}}
</button>
</div>
</div>

View File

@ -89,12 +89,12 @@
</button>
</div>
<div class="col-4 col-md-4">
<button @click.prevent="testNotifier('success')" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<i class="fa fa-vial"></i>{{loadingTest ? "Loading..." : "Test Success"}}</button>
<button @click.prevent="testNotifier('success')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Success"}}</button>
</div>
<div class="col-4 col-md-4">
<button @click.prevent="testNotifier('failure')" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<i class="fa fa-vial"></i>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
<button @click.prevent="testNotifier('failure')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
</div>
</div>

View File

@ -123,6 +123,8 @@ export default Vue.mixin({
return "bell"
case "fas fa-mobile-alt":
return "mobile"
case "fas envelope-square":
return ["fas", "envelope-square"]
case "fab fa-slack":
return ["fab", "slack-hash"]
case "fab fa-telegram-plane":

View File

@ -0,0 +1,23 @@
<template>
<div class="row mt-4">
<div class="mx-auto">
<img alt="Statping 404" class="" style="max-width:480px" src="banner.png">
</div>
<div class="col-12 mt-5 mb-5">
<div class="text-center">
<h2>Page Not Found</h2>
<h5 class="text-muted">This URL doesn't seem to be available</h5>
<router-link class="btn btn-outline-success mt-4" to="/">Back To Homepage</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'NotFound',
}
</script>

View File

@ -150,7 +150,6 @@
return {
tab: "v-pills-home-tab",
qrcode: "",
qrurl: "",
}
},
computed: {
@ -174,8 +173,8 @@
const n = await Api.notifiers()
this.$store.commit('setNotifiers', n)
this.qrurl = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURI(this.qrurl)
const u = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURIComponent(u)
this.cache = await Api.cache()
},
changeTab(e) {

View File

@ -13,6 +13,7 @@ const Setup = () => import('@/forms/Setup')
const Incidents = () => import('@/components/Dashboard/Incidents')
const Checkins = () => import('@/components/Dashboard/Checkins')
const Failures = () => import('@/components/Dashboard/Failures')
const NotFound = () => import('@/pages/NotFound')
import VueRouter from "vue-router";
import Api from "./API";
@ -131,6 +132,11 @@ const routes = [
name: 'Service',
component: Service,
props: true
},
{
path: '*',
component: NotFound,
name: 'NotFound',
}
];

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/golang/protobuf v1.3.5 // indirect
github.com/gorilla/mux v1.7.4
github.com/gorilla/securecookie v1.1.1
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9
github.com/jinzhu/gorm v1.9.12
github.com/magiconair/properties v1.8.1
github.com/mattn/go-sqlite3 v2.0.3+incompatible

2
go.sum
View File

@ -129,6 +129,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9 h1:IEhIezS5kcD4ZzOwVl8dAyJ9JCi4Xo6tg44Vj/z7UsI=
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=

View File

@ -248,8 +248,6 @@ func TestMainApiRoutes(t *testing.T) {
ExpectedContains: []string{
`go_goroutines`,
`go_memstats_alloc_bytes`,
`process_cpu_seconds_total`,
`promhttp_metric_handler_requests_total`,
`go_threads`,
},
},

View File

@ -53,7 +53,13 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r)
return
}
//notifications.OnSave(notifer.Method)
notif := services.ReturnNotifier(notifer.Method)
if _, err := notif.OnSave(); err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(vars["notifier"], "update", w, r)
}

View File

@ -70,3 +70,8 @@ func (c *commandLine) OnTest() (string, error) {
utils.Log.Infoln(out)
return out, err
}
// OnSave will trigger when this notifier is saved
func (c *commandLine) OnSave() (string, error) {
return "", nil
}

View File

@ -52,15 +52,13 @@ func (d *discord) Select() *notifications.Notification {
// OnFailure will trigger failing service
func (d *discord) OnFailure(s *services.Service, f *failures.Failure) (string, error) {
msg := `{"content": "Your service '{{.Service.Name}}' is currently failing! Reason: {{.Failure.Issue}}"}`
out, err := d.sendRequest(ReplaceVars(msg, s, f))
out, err := d.sendRequest(ReplaceVars(d.FailureData, s, f))
return out, err
}
// OnSuccess will trigger successful service
func (d *discord) OnSuccess(s *services.Service) (string, error) {
msg := `{"content": "Your service '{{.Service.Name}}' is currently online!"}`
out, err := d.sendRequest(ReplaceVars(msg, s, nil))
out, err := d.sendRequest(ReplaceVars(d.SuccessData, s, nil))
return out, err
}
@ -83,6 +81,11 @@ func (d *discord) OnTest() (string, error) {
return string(contents), nil
}
// OnSave will trigger when this notifier is saved
func (d *discord) OnSave() (string, error) {
return "", nil
}
type discordTestJson struct {
Code int `json:"code"`
Message string `json:"message"`

View File

@ -1,96 +1,21 @@
package notifiers
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/go-mail/mail"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/notifier"
"github.com/statping/statping/types/null"
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"time"
"html/template"
)
var _ notifier.Notifier = (*emailer)(nil)
const (
mainEmailTemplate = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Statping email</title>
</head>
<body style="-webkit-text-size-adjust: none; box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; height: 100%; line-height: 1.4; margin: 0; width: 100% !important;" bgcolor="#F2F4F6">
<style type="text/css">
body {
width: 100% !important;
height: 100%;
margin: 0;
line-height: 1.4;
background-color: #F2F4F6;
color: #74787E;
-webkit-text-size-adjust: none;
}
@media only screen and (max-width: 600px) {
.email-body_inner {
width: 100% !important;
}
.email-footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;" bgcolor="#F2F4F6">
<tr>
<td align="center" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;">
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0" style="-premailer-cellpadding: 0; -premailer-cellspacing: 0; border-bottom-color: #EDEFF2; border-bottom-style: solid; border-bottom-width: 1px; 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: 0; padding: 0; width: 100%; word-break: break-word;" bgcolor="#FFFFFF">
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0 auto; padding: 0; width: 570px;" bgcolor="#FFFFFF">
<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">
{{ .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 .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="{{.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 .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">
{{ .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/{{.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>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`
)
var (
mailer *mail.Dialer
)
@ -105,14 +30,11 @@ func (e *emailer) Select() *notifications.Notification {
var email = &emailer{&notifications.Notification{
Method: "email",
Title: "email",
Title: "SMTP Mail",
Description: "Send emails via SMTP when services are online or offline.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Icon: "far fa-envelope",
SuccessData: "Service {{.Service.Name}} is Back Online",
FailureData: "Service {{.Service.Name}} is Offline",
DataType: "text",
Limits: 30,
Form: []notifications.NotificationForm{{
Type: "text",
@ -145,7 +67,7 @@ var email = &emailer{&notifications.Notification{
Placeholder: "sendto@email.com",
DbField: "Var2",
}, {
Type: "text",
Type: "switch",
Title: "Disable TLS/SSL",
Placeholder: "",
SmallText: "To Disable TLS/SSL insert 'true'",
@ -166,64 +88,69 @@ type emailOutgoing struct {
// OnFailure will trigger failing service
func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) (string, error) {
subject := fmt.Sprintf("Service %s is Offline", s.Name)
tmpl := renderEmail(s, f)
email := &emailOutgoing{
To: e.Var2,
Subject: subject,
Template: mainEmailTemplate,
Data: replacer{
Service: s,
Failure: f,
},
From: e.Var1,
Template: tmpl,
From: e.Var1,
}
return "email failed", e.dialSend(email)
return tmpl, e.dialSend(email)
}
// OnSuccess will trigger successful service
func (e *emailer) OnSuccess(s *services.Service) (string, error) {
subject := fmt.Sprintf("Service %s is Back Online", s.Name)
tmpl := renderEmail(s, nil)
email := &emailOutgoing{
To: e.Var2,
Subject: subject,
Template: mainEmailTemplate,
Data: replacer{
Service: s,
Failure: &failures.Failure{},
},
From: e.Var1,
Template: tmpl,
From: e.Var1,
}
return "email sent", e.dialSend(email)
return tmpl, e.dialSend(email)
}
func renderEmail(s *services.Service, f *failures.Failure) string {
wr := bytes.NewBuffer(nil)
tmpl := template.New("email")
tmpl, err := tmpl.Parse(emailBase)
if err != nil {
log.Errorln(err)
return emailBase
}
data := replacer{
Core: core.App,
Service: s,
Failure: f,
Custom: nil,
}
if err = tmpl.ExecuteTemplate(wr, "email", data); err != nil {
log.Errorln(err)
return emailBase
}
return wr.String()
}
// OnTest triggers when this notifier has been saved
func (e *emailer) OnTest() (string, error) {
testService := services.Service{
Id: 1,
Name: "Example Service",
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 20,
LastStatusCode: 200,
Expected: null.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: utils.Now().Add(-24 * time.Hour),
}
subject := fmt.Sprintf("Service %v is Back Online", testService.Name)
service := services.Example(true)
subject := fmt.Sprintf("Service %v is Back Online", service.Name)
email := &emailOutgoing{
To: e.Var2,
Subject: subject,
Template: mainEmailTemplate,
Data: replacer{
Service: &testService,
Failure: &failures.Failure{},
},
From: e.Var1,
Template: renderEmail(service, failures.Example()),
From: e.Var1,
}
err := e.dialSend(email)
return subject, err
return subject, e.dialSend(email)
}
// OnSave will trigger when this notifier is saved
func (e *emailer) OnSave() (string, error) {
return "", nil
}
func (e *emailer) dialSend(email *emailOutgoing) error {
@ -236,14 +163,15 @@ func (e *emailer) dialSend(email *emailOutgoing) error {
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
m.SetHeader("From", email.From)
m.SetAddressHeader("From", email.From, "Statping")
m.SetHeader("To", email.To)
m.SetHeader("Subject", email.Subject)
m.SetBody("text/html", ReplaceTemplate(email.Template, email.Data))
m.SetBody("text/html", email.Template)
if err := mailer.DialAndSend(m); err != nil {
utils.Log.Errorln(fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
return err
}
return nil
}

628
notifiers/email_template.go Normal file
View File

@ -0,0 +1,628 @@
package notifiers
const emailBase = `
{{$banner := "https://assets.statping.com/greenbackground.png"}}
{{$color := "#4caf50"}}
{{if not .Service.Online}}
{{$banner = "https://assets.statping.com/offlinebanner.png"}}
{{$color = "#c30c0c"}}
{{end}}
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title> Statping Service Notification </title>
<!--[if !mso]><!-- -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style type="text/css">
@media only screen and (max-width:480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
</head>
<body style="background-color:#E7E7E7;">
<div style="background-color:#E7E7E7;">
<!-- Top Bar -->
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<v:rect style="width:600px;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
<v:fill origin="0.5, 0" position="0.5, 0" src="{{$banner}}" color="#FF3FB4" type="tile" />
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
<![endif]-->
<div style="background:#FF3FB4 url({{$banner}}) top center / auto repeat;margin:0px auto;max-width:600px;">
<div style="line-height:0;font-size:0;">
<table align="center" background="{{$banner}}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FF3FB4 url({{$banner}}) top center / auto repeat;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:45px;"> <a href="https://statping.com" target="_blank">
<img
alt="Statping" height="auto" src="https://assets.statping.com/iconlight.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="45"
/>
</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!--[if mso | IE]>
</v:textbox>
</v:rect>
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="left" style="font-size:0px;padding:15px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:22px;line-height:30px;text-align:left;color:#000000;">
{{if .Service.Online}}
{{.Service.Name}} is back online.
{{else}}
{{.Service.Name}} is currently offline, you might want to check it.
{{end}}
</div>
</td>
</tr>
<tr>
<td style="font-size:0px;padding:20px 0;padding-top:10px;padding-right:0px;padding-bottom:10px;padding-left:0px;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:10px;padding-left:0px;padding-right:0px;padding-top:10px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#626262;">
{{if .Service.Online}}
Online for {{.Service.Uptime.Human}}
{{else}}
Offline for {{.Service.Downtime.Human}}
{{end}}
</div>
</td>
</tr>
<tr>
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="{{$color}}" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:{{$color}};" valign="middle">
<a href="{{.Core.Domain}}/service/{{.Service.Id}}" style="display:inline-block;background:{{$color}};color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:4px;"
target="_blank">
View Dashboard
</a> </td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- Bottom Graphic -->
<tr>
<td style="font-size:0px;padding:0px;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="background:#fafafa;background-color:#fafafa;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#fafafa;background-color:#fafafa;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#626262;">Service Domain</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;line-height:1;text-align:left;color:#626262;">{{.Service.Domain}}</div>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:0px;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
{{if .Failure}}
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#626262;">Current Issue</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;line-height:1;text-align:left;color:#626262;">{{.Failure.Issue}}</div>
</td>
</tr>
</table>
</div>
{{end}}
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;word-break:break-word;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td height="30" style="vertical-align:top;height:30px;">
<![endif]-->
<div style="height:30px;"> &nbsp; </div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:0;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<v:rect style="width:600px;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
<v:fill origin="0.5, 0" position="0.5, 0" src="{{$banner}}" color="#F15822" type="tile" />
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
<![endif]-->
<div style="background:#F15822 url({{$banner}}) top center / auto repeat;margin:0px auto;max-width:600px;">
<div style="line-height:0;font-size:0;">
<table align="center" background="{{$banner}}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#F15822 url({{$banner}}) top center / auto repeat;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:250px;"> <a href="https://www.sphero.com" target="_blank">
<img
height="auto" src="https://assets.statping.com/statpingcom.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="250"
/>
</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!--[if mso | IE]>
</v:textbox>
</v:rect>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:20px 0;padding-top:10px;padding-bottom:0;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0;padding-top:10px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;line-height:16px;text-align:center;color:#445566;">You are receiving this email because one of your services has changed on your Statping instance. You can modify this email on the Email Notifier page in Settings.</div>
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;line-height:16px;text-align:center;color:#445566;">&copy; Statping</div>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:20px 0;padding-top:0;padding-bottom:0;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0;padding-top:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;">
<!--[if mso | IE]>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td
style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<tbody>
<tr>
<td style="vertical-align:top;padding-right:0;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;font-weight:bold;line-height:16px;text-align:center;color:#445566;"><a class="footer-link" href="https://statping.com">Statping.com</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; <a class="footer-link" href="https://github.com/statping/statping">Github</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
<a class="footer-link" href="https://statping.com/privacy">Privacy</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; <a class="footer-link" href="https://www.google.com">Unsubscribe</a></div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</div>
</body>
</html>
`

View File

@ -71,3 +71,8 @@ func (l *lineNotifier) OnTest() (string, error) {
_, err := l.sendMessage(msg)
return msg, err
}
// OnSave will trigger when this notifier is saved
func (l *lineNotifier) OnSave() (string, error) {
return "", nil
}

View File

@ -129,6 +129,11 @@ func (m *mobilePush) Send(pushMessage *pushArray) error {
return nil
}
// OnSave will trigger when this notifier is saved
func (m *mobilePush) OnSave() (string, error) {
return "", nil
}
func pushRequest(msg *pushArray) ([]byte, error) {
body, err := json.Marshal(&PushNotification{[]*pushArray{msg}})
if err != nil {

View File

@ -16,6 +16,7 @@ type replacer struct {
Core *core.Core
Service *services.Service
Failure *failures.Failure
Custom map[string]string
}
func InitNotifiers() {
@ -30,6 +31,7 @@ func InitNotifiers() {
Webhook,
Mobile,
Pushover,
statpingMailer,
)
}

View File

@ -90,3 +90,8 @@ func (t *pushover) OnTest() (string, error) {
content, err := t.sendMessage(msg)
return content, err
}
// OnSave will trigger when this notifier is saved
func (t *pushover) OnSave() (string, error) {
return "", nil
}

View File

@ -86,3 +86,8 @@ func (s *slack) OnSuccess(srv *services.Service) (string, error) {
out, err := s.sendSlack(msg)
return out, err
}
// OnSave will trigger when this notifier is saved
func (s *slack) OnSave() (string, error) {
return "", nil
}

View File

@ -0,0 +1,102 @@
package notifiers
import (
"bytes"
"encoding/json"
"github.com/statping/statping/types/core"
"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"
"time"
)
var _ notifier.Notifier = (*statpingEmailer)(nil)
const (
statpingEmailerName = "statping_emailer"
statpingEmailerHost = "https://news.statping.com"
)
type statpingEmailer struct {
*notifications.Notification
}
func (s *statpingEmailer) Select() *notifications.Notification {
return s.Notification
}
var statpingMailer = &statpingEmailer{&notifications.Notification{
Method: statpingEmailerName,
Title: "Email",
Description: "Send an email when a service becomes offline or back online using Statping's email service. You will need to verify your email address.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(10 * time.Second),
Icon: "fas envelope-square",
Limits: 60,
Form: []notifications.NotificationForm{{
Type: "email",
Title: "Send to Email Address",
Placeholder: "Insert your email address",
DbField: "Host",
Required: true,
}}},
}
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string
func (s *statpingEmailer) sendStatpingEmail(msg statpingMail) (string, error) {
data, _ := json.Marshal(msg)
resp, _, err := utils.HttpRequest(statpingEmailerHost+"/notifier", "POST", "application/json", nil, bytes.NewBuffer(data), time.Duration(10*time.Second), true, nil)
if err != nil {
return "", err
}
return string(resp), nil
}
func (s *statpingEmailer) OnTest() (string, error) {
return "", nil
}
type statpingMail struct {
Email string `json:"email"`
Core *core.Core `json:"core,omitempty"`
Service *services.Service `json:"service,omitempty"`
Failure *failures.Failure `json:"failure,omitempty"`
}
// OnFailure will trigger failing service
func (s *statpingEmailer) OnFailure(srv *services.Service, f *failures.Failure) (string, error) {
ee := statpingMail{
Email: s.Host,
Core: core.App,
Service: srv,
Failure: f,
}
return s.sendStatpingEmail(ee)
}
// OnSuccess will trigger successful service
func (s *statpingEmailer) OnSuccess(srv *services.Service) (string, error) {
ee := statpingMail{
Email: s.Host,
Core: core.App,
Service: srv,
Failure: nil,
}
return s.sendStatpingEmail(ee)
}
// OnSave will trigger when this notifier is saved
func (s *statpingEmailer) OnSave() (string, error) {
ee := statpingMail{
Email: s.Host,
Core: core.App,
Service: nil,
Failure: nil,
}
out, err := s.sendStatpingEmail(ee)
log.Println("statping emailer response", out)
return out, err
}

View File

@ -0,0 +1,61 @@
package notifiers
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/null"
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)
var (
testEmail string
)
func TestStatpingEmailerNotifier(t *testing.T) {
err := utils.InitLogs()
require.Nil(t, err)
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&notifications.Notification{})
notifications.SetDB(db)
testEmail = utils.Params.GetString("TEST_EMAIL")
statpingMailer.Host = testEmail
statpingMailer.Enabled = null.NewNullBool(true)
if testEmail == "" {
t.Log("statping email notifier testing skipped, missing TEST_EMAIL environment variable")
t.SkipNow()
}
t.Run("Load statping emailer", func(t *testing.T) {
statpingMailer.Host = testEmail
statpingMailer.Delay = time.Duration(100 * time.Millisecond)
statpingMailer.Limits = 3
Add(statpingMailer)
assert.Equal(t, "Hunter Long", statpingMailer.Author)
assert.Equal(t, testEmail, statpingMailer.Host)
})
t.Run("statping emailer Within Limits", func(t *testing.T) {
ok := statpingMailer.CanSend()
assert.True(t, ok)
})
t.Run("statping emailer OnFailure", func(t *testing.T) {
_, err := statpingMailer.OnFailure(services.Example(false), failures.Example())
assert.Nil(t, err)
})
t.Run("statping emailer OnSuccess", func(t *testing.T) {
_, err := statpingMailer.OnSuccess(services.Example(true))
assert.Nil(t, err)
})
}

View File

@ -94,6 +94,11 @@ func (t *telegram) OnTest() (string, error) {
return content, err
}
// OnSave will trigger when this notifier is saved
func (t *telegram) OnSave() (string, error) {
return "", nil
}
func telegramSuccess(res []byte) (bool, telegramResponse) {
var obj telegramResponse
json.Unmarshal(res, &obj)

View File

@ -107,6 +107,11 @@ func (t *twilio) OnTest() (string, error) {
return t.sendMessage(msg)
}
// OnSave will trigger when this notifier is saved
func (t *twilio) OnSave() (string, error) {
return "", nil
}
func twilioSuccess(res []byte) (bool, twilioResponse) {
var obj twilioResponse
json.Unmarshal(res, &obj)

View File

@ -148,3 +148,8 @@ func (w *webhooker) OnSuccess(s *services.Service) (string, error) {
content, err := ioutil.ReadAll(resp.Body)
return string(content), err
}
// OnSave will trigger when this notifier is saved
func (w *webhooker) OnSave() (string, error) {
return "", nil
}

View File

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

View File

@ -326,18 +326,13 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
return s.Online24Hours
}
// Downtime returns the amount of time of a offline service
func (s *Service) Downtime() time.Duration {
hit := s.LastHit()
fail := s.AllFailures().Last()
if hit == nil {
return time.Duration(0)
}
if fail == nil {
return utils.Now().Sub(hit.CreatedAt)
}
func (s *Service) Uptime() utils.Duration {
return utils.Duration{Duration: utils.Now().Sub(s.LastOffline)}
}
return fail.CreatedAt.Sub(hit.CreatedAt)
// Downtime returns the amount of time of a offline service
func (s *Service) Downtime() utils.Duration {
return utils.Duration{Duration: utils.Now().Sub(s.LastOnline)}
}
// ServiceOrder will reorder the services based on 'order_id' (Order)

View File

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

View File

@ -10,14 +10,14 @@ func Example(online bool) *Service {
return &Service{
Id: 6283,
Name: "Statping Example",
Domain: "https://localhost:8080",
Domain: "https://statping.com",
Expected: null.NewNullString(""),
ExpectedStatus: 200,
Interval: int(time.Duration(15 * time.Second).Seconds()),
Type: "http",
Method: "get",
PostData: null.NullString{},
Port: 0,
Port: 443,
Timeout: int(time.Duration(2 * time.Second).Seconds()),
Order: 0,
VerifySSL: null.NewNullBool(true),
@ -46,7 +46,7 @@ func Example(online bool) *Service {
AllowNotifications: null.NewNullBool(true),
UserNotified: false,
UpdateNotify: null.NewNullBool(true),
DownText: "The service ws responding with 500 status code",
DownText: "The service was responding with 500 status code",
SuccessNotified: false,
LastStatusCode: 200,
Failures: nil,
@ -55,9 +55,7 @@ func Example(online bool) *Service {
LastLatency: 124399,
LastCheck: utils.Now().Add(-37 * time.Second),
LastOnline: utils.Now().Add(-37 * time.Second),
LastOffline: utils.Now().Add((-14 * 24) * time.Hour),
SecondsOnline: int64(utils.Now().Add(-24 * time.Hour).Second()),
SecondsOffline: int64(utils.Now().Add(-150 * time.Second).Second()),
LastOffline: utils.Now().Add(-75 * time.Second),
}
}

View File

@ -432,7 +432,7 @@ func TestServices(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
amount := item.Downtime().Seconds()
assert.Equal(t, "25", fmt.Sprintf("%0.f", amount))
assert.Equal(t, "75", fmt.Sprintf("%0.f", amount))
})
t.Run("Test Failures Since", func(t *testing.T) {

View File

@ -59,9 +59,6 @@ type Service struct {
LastOnline time.Time `gorm:"-" json:"last_success" yaml:"-"`
LastOffline time.Time `gorm:"-" json:"last_error" yaml:"-"`
Stats *Stats `gorm:"-" json:"stats,omitempty" yaml:"-"`
SecondsOnline int64 `gorm:"-" json:"-" yaml:"-"`
SecondsOffline int64 `gorm:"-" json:"-" yaml:"-"`
}
type Stats struct {

View File

@ -1,7 +1,7 @@
package utils
import (
"fmt"
"github.com/hako/durafmt"
"time"
)
@ -10,58 +10,17 @@ func Now() time.Time {
return time.Now().UTC()
}
type Duration struct {
time.Duration
}
func (d Duration) Human() string {
return durafmt.Parse(d.Duration).LimitFirstN(2).String()
}
// FormatDuration converts a time.Duration into a string
func FormatDuration(d time.Duration) string {
var out string
if d.Hours() >= 24 {
out = fmt.Sprintf("%0.0f day", d.Hours()/24)
if (d.Hours() / 24) >= 2 {
out += "s"
}
return out
} else if d.Hours() >= 1 {
out = fmt.Sprintf("%0.0f hour", d.Hours())
if d.Hours() >= 2 {
out += "s"
}
return out
} else if d.Minutes() >= 1 {
out = fmt.Sprintf("%0.0f minute", d.Minutes())
if d.Minutes() >= 2 {
out += "s"
}
return out
} else if d.Seconds() >= 1 {
out = fmt.Sprintf("%0.0f second", d.Seconds())
if d.Seconds() >= 2 {
out += "s"
}
return out
} else if rev(d.Hours()) >= 24 {
out = fmt.Sprintf("%0.0f day", rev(d.Hours()/24))
if rev(d.Hours()/24) >= 2 {
out += "s"
}
return out
} else if rev(d.Hours()) >= 1 {
out = fmt.Sprintf("%0.0f hour", rev(d.Hours()))
if rev(d.Hours()) >= 2 {
out += "s"
}
return out
} else if rev(d.Minutes()) >= 1 {
out = fmt.Sprintf("%0.0f minute", rev(d.Minutes()))
if rev(d.Minutes()) >= 2 {
out += "s"
}
return out
} else {
out = fmt.Sprintf("%0.0f second", rev(d.Seconds()))
if rev(d.Seconds()) >= 2 {
out += "s"
}
}
return out
return durafmt.ParseShort(d).LimitFirstN(3).String()
}
func rev(f float64) float64 {

View File

@ -89,9 +89,9 @@ func TestDeleteFile(t *testing.T) {
func TestFormatDuration(t *testing.T) {
dur, _ := time.ParseDuration("158s")
assert.Equal(t, "3 minutes", FormatDuration(dur))
assert.Equal(t, "2 minutes 38 seconds", FormatDuration(dur))
dur, _ = time.ParseDuration("-65s")
assert.Equal(t, "1 minute", FormatDuration(dur))
assert.Equal(t, "-1 minute 5 seconds", FormatDuration(dur))
dur, _ = time.ParseDuration("3s")
assert.Equal(t, "3 seconds", FormatDuration(dur))
dur, _ = time.ParseDuration("48h")

View File

@ -1 +1 @@
0.90.54
0.90.55