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/.github/workflows/dev.yml b/.github/workflows/dev.yml index 2c46b198..e1de9143 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,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 diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d3f14b46..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: | @@ -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 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e237e1b..4c830809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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!) diff --git a/README.md b/README.md index c3854821..de709f44 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/)

@@ -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) 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/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/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/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/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/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', } ]; 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/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/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 47d5430e..1e6b37f5 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 } @@ -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"` diff --git a/notifiers/email.go b/notifiers/email.go index d53844fd..1f7b5d03 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 ) @@ -105,14 +30,11 @@ 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", 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,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: "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) +} + +// 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 } 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/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/notifiers.go b/notifiers/notifiers.go index 9910a420..4ba59fb2 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() { @@ -30,6 +31,7 @@ func InitNotifiers() { Webhook, Mobile, Pushover, + statpingMailer, ) } 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 new file mode 100644 index 00000000..444ee92c --- /dev/null +++ b/notifiers/statping_emailer.go @@ -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{¬ifications.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 +} 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/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/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 } 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/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/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 { 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") 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