From b6e117fc8ab3de548705ea837e275dc3b0c98a97 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 18 Jun 2020 17:39:10 -0700 Subject: [PATCH 01/14] 404 view --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- .github/ISSUE_TEMPLATE/issue-report.md | 4 ++-- frontend/src/pages/NotFound.vue | 23 +++++++++++++++++++++++ frontend/src/routes.js | 6 ++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 frontend/src/pages/NotFound.vue diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 326c29ef..fe7f2b99 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -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: diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md index 7088ae19..e1ad6123 100644 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -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) diff --git a/frontend/src/pages/NotFound.vue b/frontend/src/pages/NotFound.vue new file mode 100644 index 00000000..5d4ea1b5 --- /dev/null +++ b/frontend/src/pages/NotFound.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 95deca1c..be7e8a5c 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -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', } ]; From 78434207988eb31422cd935f2dd320427e0e34ed Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 18 Jun 2020 17:58:09 -0700 Subject: [PATCH 02/14] dev branch workflow --- .github/workflows/dev.yml | 207 ++++++++++++++++++++++++++++++++++---- .github/workflows/pr.yml | 167 ++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 2c46b198..818a40c6 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -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,34 @@ 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 }} - 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 +175,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: upload-release + 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 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..0d3c67c0 --- /dev/null +++ b/.github/workflows/pr.yml @@ -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 From d8c6a4423d2893cc4ab56468d79ce913e31879dc Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 18 Jun 2020 17:59:24 -0700 Subject: [PATCH 03/14] master for prerelease --- .github/workflows/master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d3f14b46..b718c73e 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -388,7 +388,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 From 1cf25c3eb01f6d962756a023877fbb773ead404b Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 18 Jun 2020 18:02:29 -0700 Subject: [PATCH 04/14] dev docker builds --- .github/workflows/dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 818a40c6..4643e513 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -299,7 +299,7 @@ jobs: delayRequest: 600 docker-release: - needs: upload-release + needs: [test, test-postman-sqlite, test-postman-mysql, test-postman-postgres] runs-on: ubuntu-latest steps: - name: Checkout Statping Repo From 66e71086f7cdd1a7fa5d04386572fe0295f28cb5 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 18 Jun 2020 18:20:00 -0700 Subject: [PATCH 05/14] version up --- CHANGELOG.md | 4 ++++ version.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e237e1b..b99df090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.90.55 (06-18-2020) +- Added 404 page +- Modified Statping's PR process, dev -> master + # 0.90.54 (06-17-2020) - Fixed Slack Notifier's failure/success data saving issue - Added additional i18n Languages (help needed!) diff --git a/version.txt b/version.txt index ccca129a..74094774 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.90.54 +0.90.55 From 2e2b4c2a7f6f8aba37ac96c9e5504a2111bc9188 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 19 Jun 2020 02:14:40 -0700 Subject: [PATCH 06/14] email notifier update, duration humanized --- frontend/src/forms/Login.vue | 2 +- frontend/src/forms/Notifier.vue | 8 +- go.mod | 1 + go.sum | 2 + notifiers/email.go | 165 +++------ notifiers/email_template.go | 628 ++++++++++++++++++++++++++++++++ notifiers/notifiers.go | 1 + types/services/methods.go | 17 +- types/services/samples.go | 10 +- types/services/struct.go | 3 - utils/time.go | 61 +--- 11 files changed, 701 insertions(+), 197 deletions(-) create mode 100644 notifiers/email_template.go diff --git a/frontend/src/forms/Login.vue b/frontend/src/forms/Login.vue index 098abe60..dd0fce1d 100644 --- a/frontend/src/forms/Login.vue +++ b/frontend/src/forms/Login.vue @@ -19,7 +19,7 @@ {{$t('dashboard.wrong_login')}} diff --git a/frontend/src/forms/Notifier.vue b/frontend/src/forms/Notifier.vue index 5ee8388d..a071dbeb 100644 --- a/frontend/src/forms/Notifier.vue +++ b/frontend/src/forms/Notifier.vue @@ -89,12 +89,12 @@
- +
- +
diff --git a/go.mod b/go.mod index 53c09597..5e1a7155 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 0739f215..892d8179 100755 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/notifiers/email.go b/notifiers/email.go index d53844fd..c9e40c85 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -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 = ` - - - - - Statping email - - - - - - - - - -` -) - var ( mailer *mail.Dialer ) @@ -110,9 +35,6 @@ var email = &emailer{¬ifications.Notification{ 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{¬ifications.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,64 @@ 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: "this is an example response", - 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) } func (e *emailer) dialSend(email *emailOutgoing) error { @@ -236,14 +158,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 } diff --git a/notifiers/email_template.go b/notifiers/email_template.go new file mode 100644 index 00000000..af21e415 --- /dev/null +++ b/notifiers/email_template.go @@ -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}} + + + + + + Statping Service Notification + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + + + +
+ +
+ + + + +
+ + + + + + +
+ + Statping + +
+
+
+ +
+
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +{{if .Service.Online}} + {{.Service.Name}} is back online. +{{else}} + {{.Service.Name}} is currently offline, you might want to check it. +{{end}} + +
+
+ +
+ + + + + + +
+ +
+ + + + + + + +
+
+{{if .Service.Online}} +Online for {{.Service.Uptime.Human}} +{{else}} +Offline for {{.Service.Downtime.Human}} +{{end}} +
+
+ + + + +
+ + View Dashboard +
+
+
+ +
+
+ +
+ +
+ + + + + + +
+ +
+ + + + + + + +
+
Service Domain
+
+
{{.Service.Domain}}
+
+
+ +
+
+ +
+ +
+ + + + + + +
+ +{{if .Failure}} +
+ + + + + + + +
+
Current Issue
+
+
{{.Failure.Issue}}
+
+
+{{end}} + +
+
+ +
+ +
 
+ +
+ +
+
+ + + + + + +
+ +
+ + + + +
+ + + + + + +
+ + + +
+
+
+ +
+
+
+ +
+ +
+ + + + + + +
+ +
+ + + + + + + +
+
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.
+
+
© Statping
+
+
+ +
+
+ +
+ +
+ + + + + + +
+ +
+ +
+ + + + + + +
+ + + + +
+
Statping.com         Github         + Privacy         Unsubscribe
+
+
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ + + +` diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index 9910a420..bdc4f5a4 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -16,6 +16,7 @@ type replacer struct { Core *core.Core Service *services.Service Failure *failures.Failure + Custom map[string]string } func InitNotifiers() { diff --git a/types/services/methods.go b/types/services/methods.go index 98842429..60132b72 100644 --- a/types/services/methods.go +++ b/types/services/methods.go @@ -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) diff --git a/types/services/samples.go b/types/services/samples.go index 6233b772..aaed0904 100644 --- a/types/services/samples.go +++ b/types/services/samples.go @@ -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), } } diff --git a/types/services/struct.go b/types/services/struct.go index ad003296..034b34d2 100644 --- a/types/services/struct.go +++ b/types/services/struct.go @@ -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 { diff --git a/utils/time.go b/utils/time.go index fd1a2394..1cbdd736 100644 --- a/utils/time.go +++ b/utils/time.go @@ -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 { From 15493fb6bae37a1f8fa5e8fbfb670088f7505e86 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 19 Jun 2020 03:32:12 -0700 Subject: [PATCH 07/14] added Statping dedicated email notifier --- notifiers/statping_emailer.go | 103 ++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 notifiers/statping_emailer.go diff --git a/notifiers/statping_emailer.go b/notifiers/statping_emailer.go new file mode 100644 index 00000000..67ab3de2 --- /dev/null +++ b/notifiers/statping_emailer.go @@ -0,0 +1,103 @@ +package notifiers + +import ( + "bytes" + "encoding/json" + "errors" + "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{¬ifications.Notification{ + Method: statpingEmailerName, + Title: "Statping Emailer", + Description: "Send an email when a service becomes offline or back online using Statping's email service.", + Author: "Hunter Long", + AuthorUrl: "https://github.com/hunterlong", + Delay: time.Duration(10 * time.Second), + Icon: "fab fa-slack", + RequestInfo: "Slack allows you to customize your own messages with many complex components. Checkout the Slack Message API to learn how you can create your own.", + 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) { + example := services.Example(true) + testMsg := ReplaceVars(s.SuccessData, example, nil) + contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true, nil) + if err != nil { + return "", err + } + defer resp.Body.Close() + if string(contents) != "ok" { + return string(contents), errors.New("the slack response was incorrect, check the URL") + } + return string(contents), nil +} + +type statpingMail struct { + Email string `json:"email"` + Core *core.Core `json:"core"` + Service *services.Service `json:"service"` + 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, + } + out, err := s.sendStatpingEmail(ee) + return out, err +} + +// 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, + } + out, err := s.sendStatpingEmail(ee) + return out, err +} From 400d0ccc2359d4750ee294779c1e7b037258cf05 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 19 Jun 2020 04:51:24 -0700 Subject: [PATCH 08/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3854821..792d6445 100644 --- a/README.md +++ b/README.md @@ -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/)

From e1b60fd7f989c486a8d0fd2852cb628e9f761def Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 19 Jun 2020 04:55:05 -0700 Subject: [PATCH 09/14] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 792d6445..de709f44 100644 --- a/README.md +++ b/README.md @@ -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) From c8e967b476df89cd37bd4b86b69ddc9a8792b2fc Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 19 Jun 2020 17:57:34 -0700 Subject: [PATCH 10/14] email notifier, uri encode qr code --- frontend/src/pages/Settings.vue | 5 ++--- notifiers/notifiers.go | 1 + notifiers/statping_emailer.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Settings.vue b/frontend/src/pages/Settings.vue index f67cdf9c..9516a9ce 100644 --- a/frontend/src/pages/Settings.vue +++ b/frontend/src/pages/Settings.vue @@ -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) { diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index bdc4f5a4..4ba59fb2 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -31,6 +31,7 @@ func InitNotifiers() { Webhook, Mobile, Pushover, + statpingMailer, ) } diff --git a/notifiers/statping_emailer.go b/notifiers/statping_emailer.go index 67ab3de2..7587e328 100644 --- a/notifiers/statping_emailer.go +++ b/notifiers/statping_emailer.go @@ -31,7 +31,7 @@ func (s *statpingEmailer) Select() *notifications.Notification { var statpingMailer = &statpingEmailer{¬ifications.Notification{ Method: statpingEmailerName, Title: "Statping Emailer", - Description: "Send an email when a service becomes offline or back online using Statping's email service.", + 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), From 7344b461d147e6233dec9703fd0460d5ff722349 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Sat, 20 Jun 2020 12:06:01 -0700 Subject: [PATCH 11/14] discord failure/success fix --- notifiers/discord.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/notifiers/discord.go b/notifiers/discord.go index 47d5430e..76892ea7 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -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 } From 386d9a49b998d0056fbbccd258ca8ed058786514 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Sat, 20 Jun 2020 21:31:08 -0700 Subject: [PATCH 12/14] test fix --- handlers/api_test.go | 2 -- types/services/services_test.go | 2 +- utils/utils_test.go | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/handlers/api_test.go b/handlers/api_test.go index e69bfb87..6fcb1550 100644 --- a/handlers/api_test.go +++ b/handlers/api_test.go @@ -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`, }, }, diff --git a/types/services/services_test.go b/types/services/services_test.go index 174e44f2..fd5fcbe2 100644 --- a/types/services/services_test.go +++ b/types/services/services_test.go @@ -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) { diff --git a/utils/utils_test.go b/utils/utils_test.go index 86ede27e..961a3315 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -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") From 723ced48c1af1f2c95b15122f95e232a6e21add8 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Sat, 20 Jun 2020 23:52:07 -0700 Subject: [PATCH 13/14] added OnSave() method for notifiers --- .github/workflows/dev.yml | 1 + .github/workflows/master.yml | 1 + CHANGELOG.md | 3 ++ handlers/notifications.go | 8 +++- notifiers/command.go | 5 +++ notifiers/discord.go | 5 +++ notifiers/email.go | 5 +++ notifiers/line_notify.go | 5 +++ notifiers/mobile.go | 5 +++ notifiers/pushover.go | 5 +++ notifiers/slack.go | 5 +++ notifiers/statping_emailer.go | 35 +++++++++-------- notifiers/statping_emailer_test.go | 61 ++++++++++++++++++++++++++++++ notifiers/telegram.go | 5 +++ notifiers/twilio.go | 5 +++ notifiers/webhook.go | 5 +++ types/notifier/interface.go | 1 + types/services/notifier.go | 1 + 18 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 notifiers/statping_emailer_test.go diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 4643e513..e1de9143 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -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: | diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index b718c73e..38fbb862 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -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: | diff --git a/CHANGELOG.md b/CHANGELOG.md index b99df090..4c830809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # 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 diff --git a/handlers/notifications.go b/handlers/notifications.go index 9fcccee1..daa82de7 100644 --- a/handlers/notifications.go +++ b/handlers/notifications.go @@ -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) } diff --git a/notifiers/command.go b/notifiers/command.go index 9c3cbfc5..6e1e5c68 100644 --- a/notifiers/command.go +++ b/notifiers/command.go @@ -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 +} diff --git a/notifiers/discord.go b/notifiers/discord.go index 76892ea7..1e6b37f5 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -81,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"` diff --git a/notifiers/email.go b/notifiers/email.go index c9e40c85..640a796b 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -148,6 +148,11 @@ func (e *emailer) OnTest() (string, error) { 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 { mailer = mail.NewDialer(e.Host, e.Port, e.Username, e.Password) m := mail.NewMessage() diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index db2dc7ec..b432517d 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -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 +} diff --git a/notifiers/mobile.go b/notifiers/mobile.go index 6ab82e31..ab05d80f 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -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 { diff --git a/notifiers/pushover.go b/notifiers/pushover.go index 0fe213f6..f480a3ad 100644 --- a/notifiers/pushover.go +++ b/notifiers/pushover.go @@ -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 +} diff --git a/notifiers/slack.go b/notifiers/slack.go index b690ecae..3c9eae3a 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -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 +} diff --git a/notifiers/statping_emailer.go b/notifiers/statping_emailer.go index 7587e328..2344a7f0 100644 --- a/notifiers/statping_emailer.go +++ b/notifiers/statping_emailer.go @@ -3,7 +3,6 @@ package notifiers import ( "bytes" "encoding/json" - "errors" "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" @@ -35,8 +34,7 @@ var statpingMailer = &statpingEmailer{¬ifications.Notification{ Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(10 * time.Second), - Icon: "fab fa-slack", - RequestInfo: "Slack allows you to customize your own messages with many complex components. Checkout the Slack Message API to learn how you can create your own.", + Icon: "fas envelope-square", Limits: 60, Form: []notifications.NotificationForm{{ Type: "email", @@ -58,23 +56,13 @@ func (s *statpingEmailer) sendStatpingEmail(msg statpingMail) (string, error) { } func (s *statpingEmailer) OnTest() (string, error) { - example := services.Example(true) - testMsg := ReplaceVars(s.SuccessData, example, nil) - contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true, nil) - if err != nil { - return "", err - } - defer resp.Body.Close() - if string(contents) != "ok" { - return string(contents), errors.New("the slack response was incorrect, check the URL") - } - return string(contents), nil + return "", nil } type statpingMail struct { Email string `json:"email"` - Core *core.Core `json:"core"` - Service *services.Service `json:"service"` + Core *core.Core `json:"core,omitempty"` + Service *services.Service `json:"service,omitempty"` Failure *failures.Failure `json:"failure,omitempty"` } @@ -86,8 +74,7 @@ func (s *statpingEmailer) OnFailure(srv *services.Service, f *failures.Failure) Service: srv, Failure: f, } - out, err := s.sendStatpingEmail(ee) - return out, err + return s.sendStatpingEmail(ee) } // OnSuccess will trigger successful service @@ -98,6 +85,18 @@ func (s *statpingEmailer) OnSuccess(srv *services.Service) (string, error) { 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 } diff --git a/notifiers/statping_emailer_test.go b/notifiers/statping_emailer_test.go new file mode 100644 index 00000000..7155cf24 --- /dev/null +++ b/notifiers/statping_emailer_test.go @@ -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(¬ifications.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) + }) + +} diff --git a/notifiers/telegram.go b/notifiers/telegram.go index 9ea8dd24..bd53cb29 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -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) diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 68c55836..da4272f3 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -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) diff --git a/notifiers/webhook.go b/notifiers/webhook.go index 03150570..30a43b6e 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -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 +} diff --git a/types/notifier/interface.go b/types/notifier/interface.go index 825b13c2..405bb927 100644 --- a/types/notifier/interface.go +++ b/types/notifier/interface.go @@ -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 } diff --git a/types/services/notifier.go b/types/services/notifier.go index 1835012b..5a822078 100644 --- a/types/services/notifier.go +++ b/types/services/notifier.go @@ -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 } From 85059bda4fd32963362a6dea3e47a67d0c29e341 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Sun, 21 Jun 2020 00:16:13 -0700 Subject: [PATCH 14/14] postman update --- dev/postman.json | 58 ++++++++++++++++++++++++++++++++--- frontend/src/mixin.js | 2 ++ notifiers/email.go | 2 +- notifiers/statping_emailer.go | 2 +- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/dev/postman.json b/dev/postman.json index fea6e87f..6bd364fb 100644 --- a/dev/postman.json +++ b/dev/postman.json @@ -3917,7 +3917,7 @@ "", "pm.test(\"View All Notifiers\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.length).to.eql(10);", + " pm.expect(jsonData.length).to.eql(11);", "});" ], "type": "text/javascript" @@ -4267,7 +4267,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"method\": \"slack\",\n \"host\": \"https://hooks.slack.com/services/EXAMPLEIDHERE/BV33WKP0C/MtKw3Kc8BFylTv4pohKqHtXX\",\n \"enabled\": true,\n \"limits\": 55\n}", + "raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.DowntimeAgo}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}", "options": { "raw": {} } @@ -4295,7 +4295,7 @@ }, { "key": "Date", - "value": "Mon, 04 May 2020 03:25:07 GMT" + "value": "Mon, 15 Jun 2020 10:15:46 GMT" }, { "key": "Connection", @@ -4307,9 +4307,59 @@ } ], "cookie": [], - "body": "{\n \"success\": false,\n \"response\": \"There's been a glitch… | Slack

There’s been a glitch…

We’re not quite sure what went wrong. You can go back, or try looking on our Help Center if you need a hand.

\\n\\n\\n\\n\",\n \"error\": {}\n}" + "body": "{\n \"success\": true,\n \"response\": \"There's been a glitch… | Slack

There’s been a glitch…

We’re not quite sure what went wrong. You can go back, or try looking on our Help Center if you need a hand.

\\n\\n\\n\\n\"\n}" } ] + }, + { + "name": "Statping Emailer", + "event": [ + { + "listen": "test", + "script": { + "id": "00f5c79e-e927-4305-b276-265b4d51b1e1", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"info@socialeck.com\",\n \"core\": {\n \"allow_reports\": true,\n \"created_at\": \"2020-06-21T05:00:12.735144154Z\",\n \"description\": \"This status page has sample data included\",\n \"domain\": \"http://localhost:8080\",\n \"footer\": null,\n \"language\": \"en\",\n \"migration_id\": 1592715612,\n \"name\": \"Statping Sample Data\",\n \"setup\": true,\n \"started_on\": \"2020-06-21T05:01:01.406134998Z\",\n \"updated_at\": \"2020-06-21T05:00:59.652965634Z\",\n \"using_cdn\": false,\n \"version\": \"0.90.54\"\n },\n \"service\": {\n \"name\": \"Statping Website\",\n \"domain\": \"https://statping.com\",\n \"last_error\": \"2020-06-21T01:01:01.406134998Z\",\n \"last_success\": \"2020-06-21T05:01:01.406134998Z\",\n \"expected\": \"\",\n \"online\": true,\n \"expected_status\": 200,\n \"check_interval\": 30,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0\n },\n \"failure\": {\n \"created_at\": \"2020-06-21T05:01:00.67942464Z\",\n \"error_code\": 406,\n \"id\": 1613,\n \"issue\": \"HTTP Status Code 406 did not match 200\",\n \"method_id\": 0,\n \"ping\": 10889\n }\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "https://news.statping.com/notifier", + "protocol": "https", + "host": [ + "news", + "statping", + "com" + ], + "path": [ + "notifier" + ] + }, + "description": "This endpoint will send emails from our servers rather than you using your own SMTP email settings. Once you save the Statping Emailer Notifier, we will send you a verification email. Once you've confirmed your email address you will recieve emails whenever your service status changes." + }, + "response": [] } ], "description": "Statping contains multiple notifiers that will send you a notification whenever a service become offline, or online. You can create your own 3rd party notifier by reading more on the [Notifiers Wiki](https://github.com/statping/statping/wiki/Notifiers) on the Github repo.", diff --git a/frontend/src/mixin.js b/frontend/src/mixin.js index e1c3cdcc..55e0cfc8 100644 --- a/frontend/src/mixin.js +++ b/frontend/src/mixin.js @@ -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": diff --git a/notifiers/email.go b/notifiers/email.go index 640a796b..1f7b5d03 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -30,7 +30,7 @@ func (e *emailer) Select() *notifications.Notification { var email = &emailer{¬ifications.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", diff --git a/notifiers/statping_emailer.go b/notifiers/statping_emailer.go index 2344a7f0..444ee92c 100644 --- a/notifiers/statping_emailer.go +++ b/notifiers/statping_emailer.go @@ -29,7 +29,7 @@ func (s *statpingEmailer) Select() *notifications.Notification { var statpingMailer = &statpingEmailer{¬ifications.Notification{ Method: statpingEmailerName, - Title: "Statping Emailer", + 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",