diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 6e14155b..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - bug - - urgent - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. If this is still an problem, please create a new issue. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: true diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 00000000..6fc2ec3b --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,162 @@ +name: Dev Release +on: + push: + branches: + - dev + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: '1.14.x' + + - 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 Node + uses: actions/setup-node@v1 + with: + node-version: '10.x' + + - name: Install Global Dependencies + run: npm install -g yarn sass cross-env + + - name: Checkout Statping Repo + uses: actions/checkout@v2 + + - uses: actions/cache@v1 + id: nodecache + with: + path: ./frontend/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Download Frontend Dependencies + if: steps.nodecache.outputs.cache-hit != 'true' + working-directory: ./frontend + run: yarn + + - uses: actions/cache@v1 + id: golangcache + with: + path: | + ~/go/pkg/mod + ~/go/bin + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - 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 frontend-build + + - name: Upload Compiled Frontend + uses: actions/upload-artifact@v1 + with: + name: static-frontend + path: /home/runner/work/statping/statping/source/dist + + test: + needs: compile + runs-on: ubuntu-latest + steps: + - name: Checkout Statping Repo + uses: actions/checkout@v2 + + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: '1.14.x' + + - 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: Install Node + uses: actions/setup-node@v1 + with: + node-version: '10.x' + + - name: Install Global Dependencies + run: npm install -g yarn sass newman cross-env wait-on @sentry/cli + + - uses: actions/cache@v1 + id: golangcache + with: + path: | + ~/go/pkg/mod + ~/go/bin + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download Go mods + if: steps.golangcache.outputs.cache-hit != 'true' + run: | + go mod download + go mod verify + make test-deps + + - name: Download Compiled Frontend + uses: actions/download-artifact@v1 + with: + name: static-frontend + path: /home/runner/work/statping/statping/source/dist + + - name: Install Statping + env: + VERSION: ${{ env.VERSION }} + run: | + make build + chmod +x statping + mv statping $(go env GOPATH)/bin/ + + - name: Go Tests + run: SASS=`which sass` go test -v -covermode=count -coverprofile=coverage.out -p=1 ./... + env: + VERSION: ${{ env.VERSION }} + DB_CONN: sqlite3 + STATPING_DIR: /home/runner/work/statping/statping + API_KEY: demopassword123 + DISABLE_LOGS: true + ALLOW_REPORTS: 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 }} + + - name: Coveralls Testing Coverage + run: goveralls -coverprofile=coverage.out -repotoken $COVERALLS + env: + COVERALLS: ${{ secrets.COVERALLS }} diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 00000000..64692e9b --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,503 @@ +name: Master Release +on: + push: + branches: + - master + paths-ignore: + - '**.md' + +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 + + 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: 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 + chmod +x statping + mv statping $(go env GOPATH)/bin/ + + - name: Go Tests + run: | + 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 + STATPING_DIR: ${{ github.workspace }} + API_KEY: demopassword123 + DISABLE_LOGS: true + ALLOW_REPORTS: 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 }} + + - name: Coveralls Testing Coverage + run: | + go get github.com/mattn/goveralls + goveralls -coverprofile=coverage.out -repotoken $COVERALLS + env: + COVERALLS: ${{ secrets.COVERALLS }} + + 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: + postmanApiKey: ${{ secrets.POSTMAN_API }} + collection: ./dev/postman.json + environment: ./dev/postman_environment.json + timeoutRequest: 15000 + delayRequest: 1000 + + build-mac: + needs: compile + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v2 + with: + go-version: '1.14.2' + - uses: actions/checkout@v2 + + - 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 Go mods + if: steps.golangcache.outputs.cache-hit != 'true' + run: | + go mod download + go mod verify + make test-deps + + - name: Download Compiled Frontend (rice-box.go) + uses: actions/download-artifact@v1 + with: + name: static-rice-box + path: ./source + + - name: Build Binaries + env: + VERSION: ${{ env.VERSION }} + COMMIT: $GITHUB_SHA + run: make build-mac + + - name: Upload MacOSX Builds + uses: actions/upload-artifact@v1 + with: + name: darwin-builds + path: ./build + + build-linux: + needs: compile + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v2 + with: + go-version: '1.14.2' + - uses: actions/checkout@v2 + + - name: Install Libraries + run: | + sudo apt update + sudo apt install libc-dev gcc-multilib build-essential musl-dev gcc g++ -y + + - 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 Go mods + if: steps.golangcache.outputs.cache-hit != 'true' + run: | + go mod download + go mod verify + make test-deps + + - name: Download Compiled Frontend (rice-box.go) + uses: actions/download-artifact@v1 + with: + name: static-rice-box + path: ./source + + - name: Build Binaries + env: + VERSION: ${{ env.VERSION }} + COMMIT: $GITHUB_SHA + run: | + go env + make build-linux + + - name: Upload Linux Builds + uses: actions/upload-artifact@v1 + with: + name: linux-builds + path: ./build + + build-windows: + needs: compile + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v2 + with: + go-version: '1.14.2' + - uses: actions/checkout@v2 + + - 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 Go mods + if: steps.golangcache.outputs.cache-hit != 'true' + run: | + go mod download + go mod verify + make test-deps + + - name: Download Compiled Frontend (rice-box.go) + uses: actions/download-artifact@v1 + with: + name: static-rice-box + path: ./source + + - name: Build Binaries + env: + VERSION: ${{ env.VERSION }} + COMMIT: $GITHUB_SHA + run: make build-win + + - name: Upload Windows Builds + uses: actions/upload-artifact@v1 + with: + name: windows-builds + path: ./build + + upload-release: + needs: [test, test-postman, build-linux, build-mac, build-windows] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - 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 Linux Builds + uses: actions/download-artifact@v1 + with: + name: linux-builds + path: ./linux + + - name: Download MacOSX Builds + uses: actions/download-artifact@v1 + with: + name: darwin-builds + path: ./darwin + + - name: Download Windows Builds + uses: actions/download-artifact@v1 + with: + name: windows-builds + path: ./windows + + - name: Upload Linux Release + id: upload-linux-asset + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} + with: + tag_name: v${{ env.VERSION }} + draft: false + prerelease: false + files: | + linux/statping-freebsd-386.tar.gz + linux/statping-freebsd-amd64.tar.gz + linux/statping-freebsd-arm.tar.gz + linux/statping-linux-386.tar.gz + linux/statping-linux-amd64.tar.gz + linux/statping-linux-arm.tar.gz + linux/statping-linux-arm64.tar.gz + linux/statping-openbsd-386.tar.gz + linux/statping-openbsd-amd64.tar.gz + linux/statping-openbsd-arm.tar.gz + linux/statping-openbsd-arm64.tar.gz + + - name: Upload MaxOSX Release + id: upload-darwin-asset + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} + with: + tag_name: v${{ env.VERSION }} + draft: false + prerelease: false + files: | + darwin/statping-darwin-386.tar.gz + darwin/statping-darwin-amd64.tar.gz + + - name: Upload Windows Release + id: upload-windows-asset + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} + with: + tag_name: v${{ env.VERSION }} + draft: false + prerelease: false + files: | + windows/statping-windows-386.zip + windows/statping-windows-amd64.zip + windows/statping-windows-arm.zip + + 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: "latest,v${{ env.VERSION }}" + buildargs: VERSION,ARCH + + sentry-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: Sentry Backend Release + uses: tclindner/sentry-releases-action@v1.0.0 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_URL: ${{ secrets.SENTRY_URL }} + SENTRY_ORG: statping + SENTRY_PROJECT: backend + with: + tagName: v${{ env.VERSION }} + environment: production + + - name: Sentry Frontend Release + uses: tclindner/sentry-releases-action@v1.0.0 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_URL: ${{ secrets.SENTRY_URL }} + SENTRY_ORG: statping + SENTRY_PROJECT: frontend + with: + tagName: v${{ env.VERSION }} + environment: production + + homebrew-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: Update Homebrew Package + env: + VERSION: ${{ env.VERSION }} + TRAVIS_API: ${{ secrets.TRAVIS_API }} + run: make publish-homebrew + + slack-update: + 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: Slack Notification + uses: rtCamp/action-slack-notify@v2.0.0 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_URL }} + SLACK_CHANNEL: dev + SLACK_USERNAME: StatpingDev diff --git a/.github/workflows/sentry.yml b/.github/workflows/sentry.yml deleted file mode 100644 index 52f47cb3..00000000 --- a/.github/workflows/sentry.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: ReleaseWorkflow - -on: - release: - types: [published, prereleased] - -jobs: - createSentryRelease: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@master - - name: Create a Sentry release - uses: tclindner/sentry-releases-action@v1.0.0 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_URL: ${{ secrets.SENTRY_URL }} - SENTRY_ORG: statping - SENTRY_PROJECT: backend - with: - tagName: ${{ github.ref }} - environment: qa diff --git a/.github/workflows/sentry_frontend.yml b/.github/workflows/sentry_frontend.yml deleted file mode 100644 index 6084f718..00000000 --- a/.github/workflows/sentry_frontend.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: ReleaseFrontendWorkflow - -on: - release: - types: [published, prereleased] - -jobs: - createSentryRelease: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@master - - name: Create a Sentry Frontend release - uses: tclindner/sentry-releases-action@v1.0.0 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_FRONTEND_AUTH_TOKEN }} - SENTRY_URL: ${{ secrets.SENTRY_URL }} - SENTRY_ORG: statping - SENTRY_PROJECT: frontend - with: - tagName: ${{ github.ref }} - environment: qa diff --git a/.github/workflows/slack-notify.yml b/.github/workflows/slack-notify.yml deleted file mode 100644 index 5215051a..00000000 --- a/.github/workflows/slack-notify.yml +++ /dev/null @@ -1,14 +0,0 @@ -on: push -name: Slack Notification -jobs: - slackNotification: - name: Slack Notification - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Slack Notification - uses: rtCamp/action-slack-notify@v2.0.0 - env: - SLACK_WEBHOOK: ${{ secrets.SLACK_URL }} - SLACK_CHANNEL: dev - SLACK_USERNAME: Github diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..7cd36cf6 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: Issues +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - name: Stale Issues + uses: actions/stale@v1.1.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "This issue hasn't had any updates in a while. If this is still a problem, please create a new issue." + days-before-stale: 30 + days-before-close: 5 + exempt-issue-labels: "bug,urgent,feature,pinned,locked" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3cb3db6c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -after_success: - - "if [[ \"$TRAVIS_BRANCH\" == \"master\" && \"$TRAVIS_PULL_REQUEST\" = \"false\" ]]; then make travis-build; fi" -before_install: - - "rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install stable" - - "nvm install 10.17.0" - - "nvm use 10.17.0 --default" -before_script: - - "mysql -e 'CREATE DATABASE IF NOT EXISTS test;'" - - "psql -c 'create database test;' -U postgres" -branches: - only: - - master - - dev -env: - global: - - "PATH=$HOME/.local/bin:$PATH" - - DB_HOST=localhost - - DB_USER=travis - - DB_PASS= - - DB_DATABASE=test - - GO_ENV=test - - STATPING_DIR=$GOPATH/src/github.com/statping/statping -go: 1.14 -go_import_path: github.com/statping/statping -install: - - "npm install -g sass newman cross-env wait-on @sentry/cli" - - "pip install --user awscli" - - "go get github.com/mattn/goveralls" - - "go mod download" - - "go mod verify" - - "make test-deps yarn clean compile install" -language: go -addons: - apt: - packages: - - libgconf-2-4 -matrix: - allow_failures: - - go: master - fast_finish: true -notifications: - email: true -os: - - linux -script: - - "travis_retry make clean test-ci" - - "if [[ \"$TRAVIS_BRANCH\" == \"master\" && \"$TRAVIS_PULL_REQUEST\" = \"false\" ]]; then make coverage; fi" -services: - - docker - - postgresql - - mysql - - mongodb diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb47bb8..48366aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +# 0.90.33 (04-24-2020) +- Fixed config loading method + +# 0.90.32 (04-23-2020) +- Modified the saving and loading process config.yml + +# 0.90.31 (04-21-2020) +- Version bump for github actions + +# 0.90.30 (04-19-2020) +- Attempt to fix Github Actions build process +- Fix for empty database connection string, and not starting in setup mode + +# 0.90.29 (04-19-2020) +- Added HTTP Redirects for services +- Removed use of SASS environment variable, now finds path or sends error +- Modified Makefile to create new snapcraft versions +- Fixed issue when logs are not initiated yet. Issue #502 +- Fixed issue when SQLite (statping.db) is not found Issue #499 +- Modified port flag in Docker image +- Fixed issue on startup without config.yml file not starting in setup mode + +# 0.90.28 (04-16-2020) +- Fixed postgres timestamp grouping +- Added postman (newman) API testing +- Added Viper and Cobra config/env parsing package +- Added more golang tests +- Modified handlers to use a more generic find method +- Added 'env' command to show variables used in config +- Added 'reset' command that will delete files and backup .db file for a fresh install +- Added error type that has common errors with http status code based on error + +# 0.90.27 (04-15-2020) +- Fixed postgres database table creation process +- Modified go build process, additional ARCHs +- Added 'SAMPLE_DATA' environment variable to disable example data on startup. (default: true) + # 0.90.26 (04-13-2020) - Fixed Delete Failures button/function - Removed timezone field from Settings (core) diff --git a/Dockerfile b/Dockerfile index aba56d78..585592a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,5 +18,4 @@ EXPOSE $PORT HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD curl -s "http://localhost:$PORT/health" | jq -r -e ".online==true" -CMD statping -port $PORT - +CMD statping --port $PORT diff --git a/Makefile b/Makefile index eef6defd..71f1d085 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update ver TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "merge_mode": "replace", "language": "go", "go": 1.14, "install": true, "sudo": "required", "services": ["docker"], "env": { "secure": "${TRVIS_SECRET}" }, "before_deploy": ["git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [{ "provider": "releases", "api_key": "$$GITHUB_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": { "branch": "master" } }], "before_script": ["rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install stable", "nvm install 10.17.0", "nvm use 10.17.0 --default", "npm install -g sass yarn cross-env", "pip install --user awscli"], "script": ["make release"], "after_success": [], "after_deploy": ["make post-release"] } } }' TEST_DIR=$(GOPATH)/src/github.com/statping/statping PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH) -OS = darwin freebsd linux openbsd +OS = freebsd linux openbsd ARCHS = 386 arm amd64 arm64 all: clean yarn-install compile docker-base docker-vue build-all @@ -39,7 +39,7 @@ release: test-deps make build-all test-ci: clean compile test-deps - SASS=`which sass` go test -v -covermode=count -coverprofile=coverage.out -p=1 ./... + DB_CONN=sqlite go test -v -covermode=count -coverprofile=coverage.out -p=1 ./... goveralls -coverprofile=coverage.out -service=travis-ci -repotoken ${COVERALLS} cypress: clean @@ -85,7 +85,7 @@ db-up: docker-compose -f dev/docker-compose.db.yml up -d --remove-orphans db-down: - docker-compose -f dev/docker-compose.full.yml down --remove-orphans + docker-compose -f dev/docker-compose.db.yml down --volumes --remove-orphans console: docker exec -t -i statping /bin/sh @@ -151,30 +151,44 @@ install-local: build generate: cd source && go generate -build-bin: - mkdir build +build-linux: + mkdir build || true export PWD=`pwd` @for arch in $(ARCHS);\ do \ for os in $(OS);\ do \ - echo "Building $$os-$$arch"; \ + echo "Building v$$VERSION for $$os-$$arch"; \ mkdir -p releases/statping-$$os-$$arch/; \ - GO111MODULE="on" GOOS=$$os GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-$$os-$$arch/statping ${PWD}/cmd; \ - chmod +x releases/statping-$$os-$$arch/statping; \ - tar -czf releases/statping-$$os-$$arch.tar.gz -C releases/statping-$$os-$$arch statping; \ + GO111MODULE="on" GOOS=$$os GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-$$os-$$arch/statping ${PWD}/cmd || true; \ + chmod +x releases/statping-$$os-$$arch/statping || true; \ + tar -czf releases/statping-$$os-$$arch.tar.gz -C releases/statping-$$os-$$arch statping || true; \ done \ done find ./releases/ -name "*.tar.gz" -type f -size +1M -exec mv "{}" build/ \; -build-win: +build-mac: + mkdir build || true export PWD=`pwd` @for arch in $(ARCHS);\ do \ - echo "Building windows-$$arch"; \ + echo "Building v$$VERSION for darwin-$$arch"; \ + mkdir -p releases/statping-darwin-$$arch/; \ + GO111MODULE="on" GOOS=darwin GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-darwin-$$arch/statping ${PWD}/cmd || true; \ + chmod +x releases/statping-darwin-$$arch/statping || true; \ + tar -czf releases/statping-darwin-$$arch.tar.gz -C releases/statping-darwin-$$arch statping || true; \ + done + find ./releases/ -name "*.tar.gz" -type f -size +1M -exec mv "{}" build/ \; + +build-win: + mkdir build || true + export PWD=`pwd` + @for arch in $(ARCHS);\ + do \ + echo "Building v$$VERSION for windows-$$arch"; \ mkdir -p releases/statping-windows-$$arch/; \ - GO111MODULE="on" GOOS=windows GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-windows-$$arch/statping.exe ${PWD}/cmd; \ - chmod +x releases/statping-windows-$$arch/statping.exe; \ + GO111MODULE="on" GOOS=windows GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-windows-$$arch/statping.exe ${PWD}/cmd || true; \ + chmod +x releases/statping-windows-$$arch/statping.exe || true; \ zip -j releases/statping-windows-$$arch.zip releases/statping-windows-$$arch/statping.exe || true; \ done find ./releases/ -name "*.zip" -type f -size +1M -exec mv "{}" build/ \; @@ -193,15 +207,14 @@ clean: rm -rf frontend/{logs,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf dev/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,test/app,plugin/*.so} rm -rf {parts,prime,snap,stage} - rm -rf dev/test/cypress/videos + rm -rf frontend/cypress/videos rm -f coverage.* sass - rm -f source/rice-box.go rm -rf **/*.db-journal rm -rf *.snap find . -name "*.out" -type f -delete find . -name "*.cpu" -type f -delete find . -name "*.mem" -type f -delete - rm -rf {build,releases,tmp} + rm -rf {build,releases,tmp,source/build,snap} print_details: @echo \==== Statping Development Instance ==== @@ -219,7 +232,7 @@ print_details: @echo \==== Monitoring and IDE ==== @echo \Grafana: http://localhost:3000 \(username: admin, password: admin\) -build-all: clean compile build-bin build-win +build-all: clean compile build-linux build-mac build-win coverage: test-deps $(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS) @@ -232,22 +245,24 @@ download-key: wget -O statping.gpg $(SIGN_URL) gpg --import statping.gpg -# build :latest docker tag -docker-build-latest: - docker build --build-arg VERSION=${VERSION} -t statping/statping:latest --no-cache -f Dockerfile . - docker tag statping/statping:latest statping/statping:v${VERSION} - # push the :dev docker tag using curl -publish-dev: - curl -H "Content-Type: application/json" --data '{"docker_tag": "dev"}' -X POST $(DOCKER) +dockerhub-dev: + docker build --build-arg VERSION=${VERSION} -t statping/statping:dev --no-cache -f Dockerfile.base . + docker push statping/statping:dev -publish-latest: publish-base - curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER) +dockerhub: + docker build --build-arg VERSION=${VERSION} -t statping/statping:base --no-cache -f Dockerfile.base . + docker build --build-arg VERSION=${VERSION} -t statping/statping:latest --no-cache -f Dockerfile . + docker tag statping/statping statping/statping:v${VERSION} + docker push statping/statping:base + docker push statping/statping:v${VERSION} + docker push statping/statping -publish-base: - curl -H "Content-Type: application/json" --data '{"docker_tag": "base"}' -X POST $(DOCKER) +docker-build-dev: + docker build --build-arg VERSION=${VERSION} -t hunterlong/statping:latest --no-cache -f Dockerfile . + docker tag hunterlong/statping:dev hunterlong/statping:dev-v${VERSION} -post-release: frontend-build upload_to_s3 publish-homebrew publish-latest +post-release: frontend-build upload_to_s3 publish-homebrew dockerhub # update the homebrew application to latest for mac publish-homebrew: @@ -274,20 +289,28 @@ sentry-release: sentry-cli releases set-commits --auto v${VERSION} sentry-cli releases finalize v${VERSION} -snapcraft: clean compile build-bin +snapcraft: clean compile build-linux + mkdir snap + mv snapcraft.yaml snap/ PWD=$(shell pwd) - snapcraft clean statping -s pull + snapcraft clean statping docker run --rm -v ${PWD}/build/statping-linux-amd64.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=amd64" - snapcraft clean statping -s pull + snapcraft clean statping docker run --rm -v ${PWD}/build/statping-linux-386.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=i386" - snapcraft clean statping -s pull + snapcraft clean statping docker run --rm -v ${PWD}/build/statping-linux-arm64.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=arm64" - snapcraft clean statping -s pull - docker run --rm -v ${PWD}/build/statping-linux-arm.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=armhf" + snapcraft clean statping + docker run --rm -v ${PWD}/build/statping-linux-arm.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=arm" snapcraft push statping_${VERSION}_amd64.snap --release stable snapcraft push statping_${VERSION}_arm64.snap --release stable snapcraft push statping_${VERSION}_i386.snap --release stable - snapcraft push statping_${VERSION}_armhf.snap --release stable + snapcraft push statping_${VERSION}_arm.snap --release stable -.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-bin build-win build-all +postman: clean compile + API_SECRET=demosecret123 statping --port=8080 > /dev/null & + sleep 3 + newman run -e dev/postman_environment.json dev/postman.json + killall statping + +.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-linux build-mac build-win build-all postman .SILENT: travis_s3_creds diff --git a/README.md b/README.md index 97f0b189..57486f02 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,15 @@ # 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. -[![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/) +[![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/)

A Future-Proof Status Page

Statping strives to remain future-proof and remain intact if a failure is created. Your Statping service should not be running on the same instance you're trying to monitor. If your server crashes your Status Page should still remaining online to notify your users of downtime. -




+ +
(dashboard login is `admin`, password `admin`) +


No Requirements

Statping is built in Go Language so all you need is the precompile binary based on your operating system. You won't need to install anything extra once you have the Statping binary installed. You can even run Statping on a Raspberry Pi. @@ -38,7 +40,7 @@ The Status binary for all other OS's is ~17Mb at most.

Mobile App is Gorgeous

-The Statping app is available on the App Store and Google Play for free. The app will allow you to view services, receive notifications when a service is offline, update groups, users, services, messages, and more! Start your own Statping server and then connect it to the app by scanning the QR code in settings. +The Statping app is available on the App Store and Google Play for free. The app will allow you to view services, receive notifications when a service is offline, update groups, users, services, messages, and more! Start your own Statping server and then connect it to the app by scanning the QR code in settings.

diff --git a/cmd/assets.go b/cmd/assets.go deleted file mode 100644 index 34600cc7..00000000 --- a/cmd/assets.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - "github.com/statping/statping/source" - "github.com/statping/statping/utils" - "net" -) - -// UsingAssets will return true if /assets folder is present -func UsingAssets() bool { - return source.UsingAssets(utils.Directory) -} - -// GetLocalIP returns the non loopback local IP of the host -func GetLocalIP() string { - addrs, err := net.InterfaceAddrs() - if err != nil { - return "http://localhost" - } - for _, address := range addrs { - // check the address type and if it is not a loopback the display it - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return fmt.Sprintf("http://%v", ipnet.IP.String()) - } - } - } - return "http://localhost" -} diff --git a/cmd/cli.go b/cmd/cli.go index a5bfbdc2..2af7da54 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -4,13 +4,15 @@ import ( "bufio" "encoding/json" "fmt" - "github.com/joho/godotenv" "github.com/pkg/errors" - "github.com/statping/statping/handlers" "github.com/statping/statping/source" + "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/configs" "github.com/statping/statping/types/core" + "github.com/statping/statping/types/groups" + "github.com/statping/statping/types/messages" "github.com/statping/statping/types/services" + "github.com/statping/statping/types/users" "github.com/statping/statping/utils" "io/ioutil" "os" @@ -18,209 +20,213 @@ import ( "time" ) -// catchCLI will run functions based on the commands sent to Statping -func catchCLI(args []string) error { +func assetsCli() error { dir := utils.Directory - runLogs := utils.InitLogs - runAssets := source.Assets + if err := utils.InitLogs(); err != nil { + return err + } + if err := source.Assets(); err != nil { + return err + } + if err := source.CreateAllAssets(dir); err != nil { + return err + } + return nil +} - switch args[0] { - case "version": - if COMMIT != "" { - fmt.Printf("%s (%s)\n", VERSION, COMMIT) - } else { - fmt.Printf("%s\n", VERSION) - } - return errors.New("end") - case "assets": - var err error - if err = runLogs(); err != nil { - return err - } - if err = runAssets(); err != nil { - return err - } - if err = source.CreateAllAssets(dir); err != nil { - return err - } - return errors.New("end") - case "sass": - if err := runLogs(); err != nil { - return err - } - if err := runAssets(); err != nil { - return err - } - if err := source.CompileSASS(source.DefaultScss...); err != nil { - return err - } - return errors.New("end") - case "update": - updateDisplay() - return errors.New("end") - case "static": - //var err error - //if err = runLogs(); err != nil { - // return err - //} - //if err = runAssets(); err != nil { - // return err - //} - //fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION) - //if _, err = core.LoadConfigFile(dir); err != nil { - // log.Errorln("config.yml file not found") - // return err - //} - //indexSource := ExportIndexHTML() - ////core.CloseDB() - //if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil { - // log.Errorln(err) - // return err - //} - //log.Infoln("Exported Statping index page: 'index.html'") - case "help": - HelpEcho() - return errors.New("end") - case "export": - var err error - var data []byte - if err = runLogs(); err != nil { - return err - } - if err = runAssets(); err != nil { - return err - } - config, err := configs.LoadConfigs() - if err != nil { - return err - } - if err = configs.ConnectConfigs(config); err != nil { - return err - } - if _, err := services.SelectAllServices(false); err != nil { - return err - } - if data, err = handlers.ExportSettings(); err != nil { - return fmt.Errorf("could not export settings: %v", err.Error()) - } - filename := fmt.Sprintf("%s/statping-%s.json", dir, time.Now().Format("01-02-2006-1504")) - if err = utils.SaveFile(filename, data); err != nil { - return fmt.Errorf("could not write file statping-export.json: %v", err.Error()) - } - log.Infoln("Statping export file saved to ", filename) - return errors.New("end") - case "import": - var err error - var data []byte - if len(args) != 2 { - return fmt.Errorf("did not include a JSON file to import\nstatping import filename.json") - } - filename := args[1] - if data, err = ioutil.ReadFile(filename); err != nil { - return err - } - var exportData handlers.ExportData - if err = json.Unmarshal(data, &exportData); err != nil { - return err - } - log.Printf("=== %s ===\n", exportData.Core.Name) - log.Printf("Services: %d\n", len(exportData.Services)) - log.Printf("Checkins: %d\n", len(exportData.Checkins)) - log.Printf("Groups: %d\n", len(exportData.Groups)) - log.Printf("Messages: %d\n", len(exportData.Messages)) - log.Printf("Users: %d\n", len(exportData.Users)) +func exportCli(args []string) error { + filename := fmt.Sprintf("%s/statping-%s.json", utils.Directory, time.Now().Format("01-02-2006-1504")) + if len(args) == 1 { + filename = fmt.Sprintf("%s/%s", utils.Directory, args) + } + var data []byte + if err := utils.InitLogs(); err != nil { + return err + } + if err := source.Assets(); err != nil { + return err + } + config, err := configs.LoadConfigs() + if err != nil { + return err + } + if err = configs.ConnectConfigs(config, false); err != nil { + return err + } + if _, err := services.SelectAllServices(false); err != nil { + return err + } + if data, err = ExportSettings(); err != nil { + return fmt.Errorf("could not export settings: %v", err.Error()) + } + if err = utils.SaveFile(filename, data); err != nil { + return fmt.Errorf("could not write file statping-export.json: %v", err.Error()) + } + log.Infoln("Statping export file saved to ", filename) + return nil +} - config, err := configs.LoadConfigs() - if err != nil { - return err - } - if err = configs.ConnectConfigs(config); err != nil { - return err - } - if data, err = handlers.ExportSettings(); err != nil { - return fmt.Errorf("could not export settings: %v", err.Error()) - } +func sassCli() error { + if err := utils.InitLogs(); err != nil { + return err + } + if err := source.Assets(); err != nil { + return err + } + if err := source.CompileSASS(source.DefaultScss...); err != nil { + return err + } + return nil +} - if ask("Import Core settings?") { - c := exportData.Core - if err := c.Update(); err != nil { +func resetCli() error { + d := utils.Directory + fmt.Println("Statping directory: ", d) + assets := d + "/assets" + if utils.FolderExists(assets) { + fmt.Printf("Deleting %s folder.\n", assets) + if err := utils.DeleteDirectory(assets); err != nil { + return err + } + } else { + fmt.Printf("Assets folder does not exist %s\n", assets) + } + + logDir := d + "/logs" + if utils.FolderExists(logDir) { + fmt.Printf("Deleting %s directory.\n", logDir) + if err := utils.DeleteDirectory(logDir); err != nil { + return err + } + } else { + fmt.Printf("Logs folder does not exist %s\n", logDir) + } + + c := d + "/config.yml" + if utils.FileExists(c) { + fmt.Printf("Deleting %s file.\n", c) + if err := utils.DeleteFile(c); err != nil { + return err + } + } else { + fmt.Printf("Config file does not exist %s\n", c) + } + + dbFile := d + "/statping.db" + if utils.FileExists(dbFile) { + fmt.Printf("Backuping up %s file.\n", dbFile) + if err := utils.RenameDirectory(dbFile, d+"/statping.db.backup"); err != nil { + return err + } + } else { + fmt.Printf("Statping SQL Database file does not exist %s\n", dbFile) + } + + fmt.Println("Statping has been reset") + return nil +} + +func envCli() error { + fmt.Println("Statping Configuration") + for k, v := range utils.Params.AllSettings() { + fmt.Printf("%s=%v\n", strings.ToUpper(k), v) + } + return nil +} + +func onceCli() error { + if err := utils.InitLogs(); err != nil { + return err + } + if err := source.Assets(); err != nil { + return err + } + log.Infoln("Running 1 time and saving to database...") + if err := runOnce(); err != nil { + return err + } + //core.CloseDB() + fmt.Println("Check is complete.") + return nil +} + +func importCli(args []string) error { + var err error + var data []byte + filename := args[1] + if data, err = ioutil.ReadFile(filename); err != nil { + return err + } + var exportData ExportData + if err = json.Unmarshal(data, &exportData); err != nil { + return err + } + log.Printf("=== %s ===\n", exportData.Core.Name) + log.Printf("Services: %d\n", len(exportData.Services)) + log.Printf("Checkins: %d\n", len(exportData.Checkins)) + log.Printf("Groups: %d\n", len(exportData.Groups)) + log.Printf("Messages: %d\n", len(exportData.Messages)) + log.Printf("Users: %d\n", len(exportData.Users)) + + config, err := configs.LoadConfigs() + if err != nil { + return err + } + if err = configs.ConnectConfigs(config, false); err != nil { + return err + } + if data, err = ExportSettings(); err != nil { + return fmt.Errorf("could not export settings: %v", err.Error()) + } + + if ask("Import Core settings?") { + c := exportData.Core + if err := c.Update(); err != nil { + return err + } + } + for _, s := range exportData.Groups { + if ask(fmt.Sprintf("Import Group '%s'?", s.Name)) { + s.Id = 0 + if err := s.Create(); err != nil { return err } } - for _, s := range exportData.Groups { - if ask(fmt.Sprintf("Import Group '%s'?", s.Name)) { - s.Id = 0 - if err := s.Create(); err != nil { - return err - } - } - } - for _, s := range exportData.Services { - if ask(fmt.Sprintf("Import Service '%s'?", s.Name)) { - s.Id = 0 - if err := s.Create(); err != nil { - return err - } - } - } - for _, s := range exportData.Checkins { - if ask(fmt.Sprintf("Import Checkin '%s'?", s.Name)) { - s.Id = 0 - if err := s.Create(); err != nil { - return err - } - } - } - for _, s := range exportData.Messages { - if ask(fmt.Sprintf("Import Message '%s'?", s.Title)) { - s.Id = 0 - if err := s.Create(); err != nil { - return err - } - } - } - for _, s := range exportData.Users { - if ask(fmt.Sprintf("Import User '%s'?", s.Username)) { - s.Id = 0 - if err := s.Create(); err != nil { - return err - } - } - } - log.Infof("Import complete") - return errors.New("end") - case "run": - if err := runLogs(); err != nil { - return err - } - if err := runAssets(); err != nil { - return err - } - log.Infoln("Running 1 time and saving to database...") - runOnce() - //core.CloseDB() - fmt.Println("Check is complete.") - return errors.New("end") - case "env": - fmt.Println("Statping Environment Variable") - if err := runLogs(); err != nil { - return err - } - if err := runAssets(); err != nil { - return err - } - envs, err := godotenv.Read(".env") - if err != nil { - log.Errorln("No .env file found in current directory.") - return err - } - for k, e := range envs { - fmt.Printf("%v=%v\n", k, e) - } - default: - return nil } - return errors.New("end") + for _, s := range exportData.Services { + if ask(fmt.Sprintf("Import Service '%s'?", s.Name)) { + s.Id = 0 + if err := s.Create(); err != nil { + return err + } + } + } + for _, s := range exportData.Checkins { + if ask(fmt.Sprintf("Import Checkin '%s'?", s.Name)) { + s.Id = 0 + if err := s.Create(); err != nil { + return err + } + } + } + for _, s := range exportData.Messages { + if ask(fmt.Sprintf("Import Message '%s'?", s.Title)) { + s.Id = 0 + if err := s.Create(); err != nil { + return err + } + } + } + for _, s := range exportData.Users { + if ask(fmt.Sprintf("Import User '%s'?", s.Username)) { + s.Id = 0 + if err := s.Create(); err != nil { + return err + } + } + } + log.Infof("Import complete") + return nil } func ask(format string) bool { @@ -231,21 +237,6 @@ func ask(format string) bool { return strings.ToLower(text) == "y" } -// ExportIndexHTML returns the HTML of the index page as a string -//func ExportIndexHTML() []byte { -// source.Assets() -// core.CoreApp.Connect(core.CoreApp., utils.Directory) -// core.SelectAllServices(false) -// core.CoreApp.UseCdn = types.NewNullBool(true) -// for _, srv := range core.Services() { -// core.CheckService(srv, true) -// } -// w := httptest.NewRecorder() -// r := httptest.NewRequest("GET", "/", nil) -// handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil) -// return w.Body.Bytes() -//} - func updateDisplay() error { gitCurrent, err := checkGithubUpdates() if err != nil { @@ -271,7 +262,7 @@ func runOnce() error { if err != nil { return errors.Wrap(err, "config.yml file not found") } - err = configs.ConnectConfigs(config) + err = configs.ConnectConfigs(config, false) if err != nil { return errors.Wrap(err, "issue connecting to database") } @@ -292,62 +283,6 @@ func runOnce() error { return nil } -// HelpEcho prints out available commands and flags for Statping -func HelpEcho() { - fmt.Printf("Statping v%v - Statping.com\n", VERSION) - fmt.Printf("A simple Application Status Monitor that is opensource and lightweight.\n") - fmt.Printf("Commands:\n") - fmt.Println(" statping - Main command to run Statping server") - fmt.Println(" statping version - Returns the current version of Statping") - fmt.Println(" statping run - Check all services 1 time and then quit") - fmt.Println(" statping assets - Dump all assets used locally to be edited.") - //fmt.Println(" statping static - Creates a static HTML file of the index page") - fmt.Println(" statping sass - Compile .scss files into the css directory") - fmt.Println(" statping env - Show all environment variables being used for Statping") - fmt.Println(" statping update - Attempts to update to the latest version") - fmt.Println(" statping export - Exports your Statping settings to a 'statping-export.json' file.") - fmt.Println(" statping import - Imports settings from a previously saved JSON file.") - fmt.Println(" statping help - Shows the user basic information about Statping") - fmt.Printf("Flags:\n") - fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)") - fmt.Println(" -port 8080 - Run HTTP server on Port (default: 8080)") - fmt.Println(" -verbose 1 - Verbose mode levels 1 - 4 (default: 1)") - fmt.Println(" -env path/debug.env - Optional .env file to set as environment variables while running server") - fmt.Printf("Environment Variables:\n") - fmt.Println(" PORT - Set the outgoing port for the HTTP server (or use -port)") - fmt.Println(" IP - Bind a specific IP address to the HTTP server (or use -ip)") - fmt.Println(" VERBOSE - Display more logs in verbose mode. (1 - 4)") - fmt.Println(" STATPING_DIR - Set a absolute path for the root path of Statping server (logs, assets, SQL db)") - fmt.Println(" DB_CONN - Automatic Database connection (sqlite, postgres, mysql)") - fmt.Println(" DB_HOST - Database hostname or IP address") - fmt.Println(" DB_USER - Database username") - fmt.Println(" DB_PASS - Database password") - fmt.Println(" DB_PORT - Database port (5432, 3306, ...)") - fmt.Println(" DB_DATABASE - Database connection's database name") - fmt.Println(" POSTGRES_SSLMODE - Enable Postgres SSL Mode 'ssl_mode=VALUE' (enable/disable/verify-full/verify-ca)") - fmt.Println(" DISABLE_LOGS - Disable viewing and writing to the log file (default is false)") - fmt.Println(" GO_ENV - Run Statping in testmode, will bypass HTTP authentication (if set as 'test')") - fmt.Println(" NAME - Set a name for the Statping status page") - fmt.Println(" DESCRIPTION - Set a description for the Statping status page") - fmt.Println(" DOMAIN - Set a URL for the Statping status page") - fmt.Println(" ADMIN_USER - Username for administrator account (default: admin)") - fmt.Println(" ADMIN_PASS - Password for administrator account (default: admin)") - fmt.Println(" SASS - Set the absolute path to the sass binary location") - fmt.Println(" USE_ASSETS - Automatically use assets from 'assets folder' (true/false)") - fmt.Println(" HTTP_PROXY - Use a HTTP Proxy for HTTP Requests") - fmt.Println(" AUTH_USERNAME - HTTP Basic Authentication username") - fmt.Println(" AUTH_PASSWORD - HTTP Basic Authentication password") - fmt.Println(" BASE_PATH - Set the base URL prefix (set to 'monitor' if URL is domain.com/monitor)") - fmt.Println(" PREFIX - A Prefix for each value in Prometheus /metric exporter") - fmt.Println(" API_KEY - Set a custom API Key for Statping") - fmt.Println(" API_SECRET - Set a custom API Secret for API Authentication") - fmt.Println(" MAX_OPEN_CONN - Set Maximum Open Connections for database server (default: 5)") - fmt.Println(" MAX_IDLE_CONN - Set Maximum Idle Connections for database server") - fmt.Println(" MAX_LIFE_CONN - Set Maximum Life Connections for database server") - fmt.Println(" * You can insert environment variables into a '.env' file in root directory.") - fmt.Println("Give Statping a Star at https://github.com/statping/statping") -} - func checkGithubUpdates() (githubResponse, error) { url := "https://api.github.com/repos/statping/statping/releases/latest" contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true) @@ -437,3 +372,56 @@ type gitUploader struct { Type string `json:"type"` SiteAdmin bool `json:"site_admin"` } + +// ExportChartsJs renders the charts for the index page + +type ExportData struct { + Core *core.Core `json:"core"` + Services []services.Service `json:"services"` + Messages []*messages.Message `json:"messages"` + Checkins []*checkins.Checkin `json:"checkins"` + Users []*users.User `json:"users"` + Groups []*groups.Group `json:"groups"` + Notifiers []core.AllNotifiers `json:"notifiers"` +} + +// ExportSettings will export a JSON file containing all of the settings below: +// - Core +// - Notifiers +// - Checkins +// - Users +// - Services +// - Groups +// - Messages +func ExportSettings() ([]byte, error) { + c, err := core.Select() + if err != nil { + return nil, err + } + data := ExportData{ + Core: c, + //Notifiers: notifications.All(), + Checkins: checkins.All(), + Users: users.All(), + Services: services.AllInOrder(), + Groups: groups.All(), + Messages: messages.All(), + } + export, err := json.Marshal(data) + return export, err +} + +// ExportIndexHTML returns the HTML of the index page as a string +//func ExportIndexHTML() []byte { +// source.Assets() +// core.CoreApp.Connect(core.CoreApp., utils.Directory) +// core.SelectAllServices(false) +// core.CoreApp.UseCdn = types.NewNullBool(true) +// for _, srv := range core.Services() { +// core.CheckService(srv, true) +// } +// w := httptest.NewRecorder() +// r := httptest.NewRequest("GET", "/", nil) +// handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil) +// return w.Body.Bytes() +//} diff --git a/cmd/cli_test.go b/cmd/cli_test.go index 343d1102..1730cadf 100644 --- a/cmd/cli_test.go +++ b/cmd/cli_test.go @@ -1,14 +1,12 @@ package main import ( - "github.com/rendon/testcli" + "bytes" "github.com/statping/statping/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "os" - "os/exec" + "io/ioutil" "testing" - "time" ) var ( @@ -16,169 +14,95 @@ var ( ) func init() { - dir = utils.Directory - //core.SampleHits = 480 + utils.InitCLI() } -func TestStartServerCommand(t *testing.T) { - t.SkipNow() - os.Setenv("DB_CONN", "sqlite") - cmd := helperCommand(nil, "") - var got = make(chan string) - commandAndSleep(cmd, time.Duration(60*time.Second), got) - os.Unsetenv("DB_CONN") - gg, _ := <-got - assert.Contains(t, gg, "DB_CONN environment variable was found") - assert.Contains(t, gg, "Core database does not exist, creating now!") - assert.Contains(t, gg, "Starting monitoring process for 5 Services") -} +func TestStatpingDirectory(t *testing.T) { + dir := utils.Directory + require.NotContains(t, dir, "/cmd") + require.NotEmpty(t, dir) -func TestVersionCommand(t *testing.T) { - c := testcli.Command("statping", "version") - c.Run() - assert.True(t, c.StdoutContains(VERSION)) -} - -func TestHelpCommand(t *testing.T) { - c := testcli.Command("statping", "help") - c.Run() - t.Log(c.Stdout()) - assert.True(t, c.StdoutContains("statping help - Shows the user basic information about Statping")) -} - -func TestStaticCommand(t *testing.T) { - t.SkipNow() - cmd := helperCommand(nil, "static") - var got = make(chan string) - commandAndSleep(cmd, time.Duration(10*time.Second), got) - gg, _ := <-got - t.Log(gg) - assert.Contains(t, gg, "Exporting Static 'index.html' page...") - assert.Contains(t, gg, "Exported Statping index page: 'index.html'") - assert.FileExists(t, dir+"/index.html") -} - -func TestExportCommand(t *testing.T) { - t.SkipNow() - cmd := helperCommand(nil, "export") - var got = make(chan string) - commandAndSleep(cmd, time.Duration(10*time.Second), got) - gg, _ := <-got - t.Log(gg) - assert.FileExists(t, dir+"/statping-export.json") -} - -func TestUpdateCommand(t *testing.T) { - t.SkipNow() - cmd := helperCommand(nil, "version") - var got = make(chan string) - commandAndSleep(cmd, time.Duration(15*time.Second), got) - gg, _ := <-got - t.Log(gg) - assert.Contains(t, gg, VERSION) -} - -func TestAssetsCommand(t *testing.T) { - t.SkipNow() - c := testcli.Command("statping", "assets") - c.Run() - t.Log(c.Stdout()) - t.Log("Directory for Assets: ", dir) - time.Sleep(1 * time.Second) - err := utils.DeleteDirectory(dir + "/assets") - require.Nil(t, err) - assert.FileExists(t, dir+"/assets/robots.txt") - assert.FileExists(t, dir+"/assets/scss/base.scss") - assert.FileExists(t, dir+"/assets/scss/main.scss") - assert.FileExists(t, dir+"/assets/scss/variables.scss") - assert.FileExists(t, dir+"/assets/css/main.css") - assert.FileExists(t, dir+"/assets/css/vendor.css") - assert.FileExists(t, dir+"/assets/css/style.css") - err = utils.DeleteDirectory(dir + "/assets") - require.Nil(t, err) -} - -func TestRunCommand(t *testing.T) { - t.SkipNow() - cmd := helperCommand(nil, "run") - var got = make(chan string) - commandAndSleep(cmd, time.Duration(15*time.Second), got) - gg, _ := <-got - t.Log(gg) - assert.Contains(t, gg, "Running 1 time and saving to database...") - assert.Contains(t, gg, "Check is complete.") -} - -func TestEnvironmentVarsCommand(t *testing.T) { - c := testcli.Command("statping", "env") - c.Run() - assert.True(t, c.StdoutContains("Statping Environment Variable")) -} - -func TestVersionCLI(t *testing.T) { - run := catchCLI([]string{"version"}) - assert.EqualError(t, run, "end") -} - -func TestAssetsCLI(t *testing.T) { - catchCLI([]string{"assets"}) - //assert.EqualError(t, run, "end") - assert.FileExists(t, dir+"/assets/css/main.css") - assert.FileExists(t, dir+"/assets/css/style.css") - assert.FileExists(t, dir+"/assets/css/vendor.css") - assert.FileExists(t, dir+"/assets/scss/base.scss") - assert.FileExists(t, dir+"/assets/scss/mobile.scss") - assert.FileExists(t, dir+"/assets/scss/variables.scss") -} - -func TestSassCLI(t *testing.T) { - t.SkipNow() - catchCLI([]string{"sass"}) - assert.FileExists(t, dir+"/assets/css/main.css") - assert.FileExists(t, dir+"/assets/css/style.css") - assert.FileExists(t, dir+"/assets/css/vendor.css") -} - -func TestUpdateCLI(t *testing.T) { - t.SkipNow() - cmd := helperCommand(nil, "update") - var got = make(chan string) - commandAndSleep(cmd, time.Duration(15*time.Second), got) - gg, _ := <-got - t.Log(gg) - assert.Contains(t, gg, "version") -} - -func TestHelpCLI(t *testing.T) { - run := catchCLI([]string{"help"}) - assert.EqualError(t, run, "end") -} - -func TestRunOnceCLI(t *testing.T) { - t.SkipNow() - run := catchCLI([]string{"run"}) - assert.EqualError(t, run, "end") + dir = utils.Params.GetString("STATPING_DIR") + require.NotContains(t, dir, "/cmd") + require.NotEmpty(t, dir) } func TestEnvCLI(t *testing.T) { - run := catchCLI([]string{"env"}) - assert.Error(t, run) + cmd := rootCmd + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"env"}) + err := cmd.Execute() + require.Nil(t, err) + out, err := ioutil.ReadAll(b) + require.Nil(t, err) + assert.Contains(t, string(out), VERSION) + assert.Contains(t, utils.Directory, string(out)) + assert.Contains(t, "SAMPLE_DATA=true", string(out)) } -func commandAndSleep(cmd *exec.Cmd, duration time.Duration, out chan<- string) { - go func(out chan<- string) { - runCommand(cmd, out) - }(out) - time.Sleep(duration) - cmd.Process.Kill() +func TestVersionCLI(t *testing.T) { + cmd := rootCmd + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"version"}) + err := cmd.Execute() + require.Nil(t, err) + out, err := ioutil.ReadAll(b) + require.Nil(t, err) + assert.Contains(t, VERSION, string(out)) } -func helperCommand(envs []string, s ...string) *exec.Cmd { - cmd := exec.Command("statping", s...) - return cmd +func TestAssetsCLI(t *testing.T) { + cmd := rootCmd + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"assets"}) + err := cmd.Execute() + require.Nil(t, err) + out, err := ioutil.ReadAll(b) + assert.Nil(t, err) + assert.Contains(t, string(out), VERSION) + assert.FileExists(t, utils.Directory+"/assets/css/main.css") + assert.FileExists(t, utils.Directory+"/assets/css/style.css") + assert.FileExists(t, utils.Directory+"/assets/css/vendor.css") + assert.FileExists(t, utils.Directory+"/assets/scss/base.scss") + assert.FileExists(t, utils.Directory+"/assets/scss/mobile.scss") + assert.FileExists(t, utils.Directory+"/assets/scss/variables.scss") } -func runCommand(c *exec.Cmd, out chan<- string) { - bout, _ := c.CombinedOutput() - out <- string(bout) +func TestHelpCLI(t *testing.T) { + cmd := rootCmd + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"help"}) + err := cmd.Execute() + require.Nil(t, err) + out, err := ioutil.ReadAll(b) + require.Nil(t, err) + assert.Contains(t, string(out), VERSION) +} + +func TestResetCLI(t *testing.T) { + err := utils.SaveFile(utils.Directory+"/statping.db", []byte("test data")) + require.Nil(t, err) + + cmd := rootCmd + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"reset"}) + err = cmd.Execute() + require.Nil(t, err) + out, err := ioutil.ReadAll(b) + require.Nil(t, err) + assert.Contains(t, string(out), VERSION) + + assert.NoDirExists(t, utils.Directory+"/assets") + assert.NoDirExists(t, utils.Directory+"/logs") + assert.NoFileExists(t, utils.Directory+"/config.yml") + assert.NoFileExists(t, utils.Directory+"/statping.db") + assert.FileExists(t, utils.Directory+"/statping.db.backup") + + err = utils.DeleteFile(utils.Directory + "/statping.db.backup") + require.Nil(t, err) } diff --git a/cmd/commands.go b/cmd/commands.go new file mode 100644 index 00000000..6054ecce --- /dev/null +++ b/cmd/commands.go @@ -0,0 +1,95 @@ +package main + +import ( + "fmt" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "statping", + Short: "A simple Application Status Monitor that is opensource and lightweight.", + Run: func(cmd *cobra.Command, args []string) { + start() + }, +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Statping", + Run: func(cmd *cobra.Command, args []string) { + if COMMIT != "" { + fmt.Printf("%s (%s)\n", VERSION, COMMIT) + } else { + fmt.Printf("%s\n", VERSION) + } + }, +} + +var assetsCmd = &cobra.Command{ + Use: "assets", + Short: "Dump all assets used locally to be edited", + RunE: func(cmd *cobra.Command, args []string) error { + return assetsCli() + }, +} + +var exportCmd = &cobra.Command{ + Use: "export", + Short: "Exports your Statping settings to a 'statping-export.json' file.", + RunE: func(cmd *cobra.Command, args []string) error { + return exportCli(args) + }, +} + +var sassCmd = &cobra.Command{ + Use: "sass", + Short: "Compile .scss files into the css directory", + RunE: func(cmd *cobra.Command, args []string) error { + return sassCli() + }, +} + +var envCmd = &cobra.Command{ + Use: "env", + Short: "Return the configs that will be ran", + RunE: func(cmd *cobra.Command, args []string) error { + return envCli() + }, +} + +var resetCmd = &cobra.Command{ + Use: "reset", + Short: "Start a fresh copy of Statping", + RunE: func(cmd *cobra.Command, args []string) error { + return resetCli() + }, +} + +var onceCmd = &cobra.Command{ + Use: "once", + Short: "Check all services 1 time and then quit", + RunE: func(cmd *cobra.Command, args []string) error { + return onceCli() + }, +} + +var importCmd = &cobra.Command{ + Use: "import [.json file]", + Short: "Imports settings from a previously saved JSON file.", + RunE: func(cmd *cobra.Command, args []string) error { + return importCli(args) + }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires input file (.json)") + } + return nil + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + exit(err) + } +} diff --git a/cmd/flags.go b/cmd/flags.go new file mode 100644 index 00000000..98b1d0cf --- /dev/null +++ b/cmd/flags.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func parseFlags(cmd *cobra.Command) { + cmd.PersistentFlags().StringVarP(&ipAddress, "ip", "s", "0.0.0.0", "server port") + viper.BindPFlag("ip", cmd.PersistentFlags().Lookup("ip")) + + cmd.PersistentFlags().IntVarP(&port, "port", "p", 8080, "server port") + viper.BindPFlag("port", cmd.PersistentFlags().Lookup("port")) + + cmd.PersistentFlags().IntVarP(&verboseMode, "verbose", "v", 2, "server port") + viper.BindPFlag("verbose", cmd.PersistentFlags().Lookup("verbose")) +} diff --git a/cmd/main.go b/cmd/main.go index 9d147cd6..f578f957 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,7 +1,6 @@ package main import ( - "flag" "fmt" "github.com/pkg/errors" "github.com/statping/statping/database" @@ -31,26 +30,18 @@ var ( confgs *configs.DbConfig ) -// parseFlags will parse the application flags -// -ip = 0.0.0.0 IP address for outgoing HTTP server -// -port = 8080 Port number for outgoing HTTP server -// environment variables WILL overwrite flags -func parseFlags() { - envPort := utils.Getenv("PORT", 8080).(int) - envIpAddress := utils.Getenv("IP", "0.0.0.0").(string) - envVerbose := utils.Getenv("VERBOSE", 2).(int) - //envGrpcPort := utils.Getenv("GRPC_PORT", 0).(int) - - flag.StringVar(&ipAddress, "ip", envIpAddress, "IP address to run the Statping HTTP server") - flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server") - flag.IntVar(&port, "port", envPort, "Port to run the HTTP server") - //flag.IntVar(&grpcPort, "grpc", envGrpcPort, "Port to run the gRPC server") - flag.IntVar(&verboseMode, "verbose", envVerbose, "Run in verbose mode to see detailed logs (1 - 4)") - flag.Parse() -} - func init() { core.New(VERSION) + rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(assetsCmd) + rootCmd.AddCommand(exportCmd) + rootCmd.AddCommand(importCmd) + rootCmd.AddCommand(sassCmd) + rootCmd.AddCommand(onceCmd) + rootCmd.AddCommand(envCmd) + rootCmd.AddCommand(resetCmd) + utils.InitCLI() + parseFlags(rootCmd) } // exit will return an error and return an exit code 1 due to this error @@ -69,11 +60,14 @@ func Close() { // main will run the Statping application func main() { + Execute() +} + +// main will run the Statping application +func start() { var err error go sigterm() - parseFlags() - if err := source.Assets(); err != nil { exit(err) } @@ -84,32 +78,21 @@ func main() { log.Errorf("Statping Log Error: %v\n", err) } - args := flag.Args() - - if len(args) >= 1 { - err := catchCLI(args) - if err != nil { - if err.Error() == "end" { - os.Exit(0) - return - } - exit(err) - } - } log.Info(fmt.Sprintf("Starting Statping v%s", VERSION)) - if err := updateDisplay(); err != nil { - log.Warnln(err) - } + //if err := updateDisplay(); err != nil { + // log.Warnln(err) + //} confgs, err = configs.LoadConfigs() if err != nil { + log.Infoln("Starting in Setup Mode") if err := SetupMode(); err != nil { exit(err) } } - if err = configs.ConnectConfigs(confgs); err != nil { + if err = configs.ConnectConfigs(confgs, true); err != nil { exit(err) } @@ -135,8 +118,10 @@ func main() { exit(errors.Wrap(err, "error creating default admin user")) } - if err := configs.TriggerSamples(); err != nil { - exit(errors.Wrap(err, "error creating database")) + if utils.Params.GetBool("SAMPLE_DATA") { + if err := configs.TriggerSamples(); err != nil { + exit(errors.Wrap(err, "error creating database")) + } } } @@ -149,12 +134,6 @@ func main() { exit(err) } - //log.Infoln("Migrating Notifiers...") - //if err := notifier.Migrate(); err != nil { - // exit(errors.Wrap(err, "error migrating notifiers")) - //} - //log.Infoln("Notifiers Migrated") - if err := mainProcess(); err != nil { exit(err) } diff --git a/database/database.go b/database/database.go index eaa17fff..479aa9ce 100644 --- a/database/database.go +++ b/database/database.go @@ -92,6 +92,7 @@ type Database interface { // extra Error() error + Status() int RowsAffected() int64 Since(time.Time) Database @@ -156,7 +157,10 @@ type Db struct { // Openw is a drop-in replacement for Open() func Openw(dialect string, args ...interface{}) (db Database, err error) { gorm.NowFunc = func() time.Time { - return time.Now().UTC() + return utils.Now() + } + if dialect == "sqlite" { + dialect = "sqlite3" } gormdb, err := gorm.Open(dialect, args...) if err != nil { @@ -167,22 +171,36 @@ func Openw(dialect string, args ...interface{}) (db Database, err error) { } func OpenTester() (Database, error) { - testDB := utils.Getenv("TEST_DB", "sqlite3").(string) - var dbParamsstring string + testDB := utils.Params.GetString("DB_CONN") + var dbString string + switch testDB { case "mysql": - dbParamsstring = fmt.Sprintf("root:password123@tcp(localhost:3306)/statping?charset=utf8&parseTime=True&loc=UTC&time_zone=%%27UTC%%27") + dbString = fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=utf8&parseTime=True&loc=UTC&time_zone=%%27UTC%%27", + utils.Params.GetString("DB_HOST"), + utils.Params.GetString("DB_PASS"), + utils.Params.GetString("DB_HOST"), + utils.Params.GetInt("DB_PORT"), + utils.Params.GetString("DB_DATABASE"), + ) case "postgres": - dbParamsstring = fmt.Sprintf("host=localhost port=5432 user=root dbname=statping password=password123 timezone=UTC") + dbString = fmt.Sprintf("host=%s port=%v user=%s dbname=%s password=%s sslmode=disable timezone=UTC", + utils.Params.GetString("DB_HOST"), + utils.Params.GetInt("DB_PORT"), + utils.Params.GetString("DB_USER"), + utils.Params.GetString("DB_DATABASE"), + utils.Params.GetString("DB_PASS")) default: - dbParamsstring = fmt.Sprintf("file:%s?mode=memory&cache=shared", utils.RandomString(12)) + dbString = fmt.Sprintf("file:%s?mode=memory&cache=shared", utils.RandomString(12)) } - fmt.Println(testDB, dbParamsstring) - newDb, err := Openw(testDB, dbParamsstring) + newDb, err := Openw(testDB, dbString) if err != nil { return nil, err } newDb.DB().SetMaxOpenConns(1) + if testDB != "sqlite3" { + newDb.DB().SetMaxOpenConns(25) + } return newDb, err } @@ -490,6 +508,34 @@ func (it *Db) Error() error { return it.Database.Error } +func (it *Db) Status() int { + switch it.Database.Error { + case gorm.ErrRecordNotFound: + return 404 + case gorm.ErrCantStartTransaction: + return 422 + case gorm.ErrInvalidSQL: + return 500 + case gorm.ErrUnaddressable: + return 500 + default: + return 500 + } +} + +func (it *Db) Loggable() bool { + switch it.Database.Error { + case gorm.ErrCantStartTransaction: + return true + case gorm.ErrInvalidSQL: + return true + case gorm.ErrUnaddressable: + return true + default: + return false + } +} + func (it *Db) Since(ago time.Time) Database { return it.Where("created_at > ?", it.FormatTime(ago)) } diff --git a/database/grouping.go b/database/grouping.go index 6c76b821..54544870 100644 --- a/database/grouping.go +++ b/database/grouping.go @@ -74,7 +74,7 @@ func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) { dbQuery := g.db.MultipleSelects( g.db.SelectByTime(g.Group), by.String(), - ).Group("timeframe") + ).Group("timeframe").Order("timeframe", true) g.db = dbQuery diff --git a/database/routines.go b/database/routines.go index c92d5116..0445c15c 100644 --- a/database/routines.go +++ b/database/routines.go @@ -17,8 +17,8 @@ var ( // Maintenance will automatically delete old records from 'failures' and 'hits' // this function is currently set to delete records 7+ days old every 60 minutes func Maintenance() { - dur := utils.GetenvAs("REMOVE_AFTER", "2160h").Duration() - interval := utils.GetenvAs("CLEANUP_INTERVAL", "1h").Duration() + dur := utils.Params.GetDuration("REMOVE_AFTER") + interval := utils.Params.GetDuration("CLEANUP_INTERVAL") log.Infof("Database Cleanup runs every %s and will remove records older than %s", interval.String(), dur.String()) ticker := interval diff --git a/database/time.go b/database/time.go index 3b25755c..ea41f77d 100644 --- a/database/time.go +++ b/database/time.go @@ -36,7 +36,7 @@ func (it *Db) SelectByTime(increment time.Duration) string { case "mysql": return fmt.Sprintf("FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(created_at) / %d) * %d) AS timeframe", seconds, seconds) case "postgres": - return fmt.Sprintf("date_trunc('%s', created_at) AS timeframe", increment) + return fmt.Sprintf("date_trunc('minute', created_at) - (CAST(EXTRACT(MINUTE FROM created_at) AS integer) %% %d) * interval '1 minute' AS timeframe", seconds) default: return fmt.Sprintf("datetime((strftime('%%s', created_at) / %d) * %d, 'unixepoch') as timeframe", seconds, seconds) } diff --git a/dev/docker-compose.db.yml b/dev/docker-compose.db.yml index 6588a813..c76a1254 100644 --- a/dev/docker-compose.db.yml +++ b/dev/docker-compose.db.yml @@ -5,14 +5,12 @@ services: postgres: container_name: postgres image: postgres - volumes: - - ../docker/databases/postgres:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password123 POSTGRES_DB: statping POSTGRES_USER: root ports: - - 5432:5432 + - "127.0.0.1:5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U root"] interval: 15s @@ -22,8 +20,6 @@ services: mysql: container_name: mysql image: mysql:5.7 - volumes: - - ../docker/databases/mysql:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: password123 @@ -31,7 +27,7 @@ services: MYSQL_USER: root MYSQL_PASSWORD: password123 ports: - - 3306:3306 + - "127.0.0.1:3306:3306" healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] timeout: 20s @@ -46,7 +42,7 @@ services: mysql: condition: service_healthy ports: - - 5050:80 + - "127.0.0.1:5050:80" links: - mysql:db environment: @@ -62,7 +58,7 @@ services: restart: on-failure command: sqlite_web -H 0.0.0.0 -r -x /data/statping.db ports: - - 6050:8080 + - "127.0.0.1:6050:8080" volumes: - ../docker/statping/sqlite/statping.db:/data/statping.db:ro environment: @@ -75,11 +71,8 @@ services: environment: DEFAULT_USER: admin@admin.com DEFAULT_PASSWORD: admin - depends_on: - postgres: - condition: service_healthy ports: - - 7000:5050 + - "127.0.0.1:7000:5050" links: - postgres:postgres @@ -91,7 +84,7 @@ services: - ./prometheus.yml:/etc/prometheus/prometheus.yml - ../docker/databases/prometheus:/prometheus ports: - - 7050:9090 + - "127.0.0.1:7050:9090" healthcheck: test: "/bin/wget -q -Y off http://localhost:9090/status -O /dev/null > /dev/null 2>&1" interval: 10s diff --git a/dev/postman.json b/dev/postman.json index 7b0f400e..40dc0dda 100644 --- a/dev/postman.json +++ b/dev/postman.json @@ -2,10 +2,268 @@ "info": { "_postman_id": "94807b85-ef65-4370-9144-b1a74e04cb0e", "name": "Statping", - "description": "Statping API Documentation for all endpoints to manage your services, users, groups, notifiers, messages, and more. \n\n## API Requirements\n\n- `endpoint` variable should be the URL of your Statping instance.\n- `api_key` variable is the API Secret key from the Settings page and is used for the Authorization Bearer token.\n\n", + "description": "Statping API Requests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ + { + "name": "Miscellaneous", + "item": [ + { + "name": "Statping Setup", + "event": [ + { + "listen": "test", + "script": { + "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Check Core API Route\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.config.connection).to.eql(\"sqlite3\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "db_host", + "value": "localhost", + "type": "text" + }, + { + "key": "db_user", + "value": "root", + "type": "text" + }, + { + "key": "db_password", + "value": "password123", + "type": "text" + }, + { + "key": "db_database", + "value": "statping", + "type": "text" + }, + { + "key": "db_connection", + "value": "sqlite", + "type": "text" + }, + { + "key": "db_port", + "value": "3306", + "type": "text" + }, + { + "key": "project", + "value": "Statping Monitoring Sample Data", + "type": "text" + }, + { + "key": "description", + "value": "View All Example Services", + "type": "text" + }, + { + "key": "username", + "value": "admin", + "type": "text" + }, + { + "key": "password", + "value": "admin", + "type": "text" + }, + { + "key": "domain", + "value": "http://localhost:8080", + "type": "text" + }, + { + "key": "email", + "value": "info@domain.com", + "type": "text" + }, + { + "key": "sample_data", + "value": "true", + "type": "text" + } + ], + "options": { + "urlencoded": {} + } + }, + "url": { + "raw": "{{endpoint}}/api/setup", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "setup" + ] + }, + "description": "The root API endpoint to view basic Statping configuration including Name, URL, database type, and other useful fields." + }, + "response": [] + }, + { + "name": "Statping Details", + "event": [ + { + "listen": "test", + "script": { + "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Check Core API Route\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.name).to.eql(\"Statping Monitoring Sample Data\");", + " pm.expect(jsonData.using_cdn).to.eql(false);", + " pm.expect(jsonData.admin).to.eql(false);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api" + ] + }, + "description": "The root API endpoint to view basic Statping configuration including Name, URL, database type, and other useful fields." + }, + "response": [] + }, + { + "name": "Statping Clear Cache", + "event": [ + { + "listen": "test", + "script": { + "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{endpoint}}/api/clear_cache", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "clear_cache" + ] + }, + "description": "This endpoint will clear all the cache files in your Statping instance. This includes chart data and service views." + }, + "response": [] + } + ], + "description": "This is for Statping's miscellaneous API endpoints that aren't a part of another category.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "883519e8-7c7d-49c0-9812-d988d0179907", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "7a0738e6-2fc4-45cb-9f1a-1cd57fb76b66", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, { "name": "Services", "item": [ @@ -72,8 +330,6 @@ "pm.test(\"View Service\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.name).to.eql(\"Google\");", - " pm.expect(jsonData.status_code).to.eql(200);", - " pm.expect(jsonData.type).to.eql(\"http\");", "});" ], "type": "text/javascript" @@ -197,7 +453,7 @@ } ], "url": { - "raw": "{{endpoint}}/api/services/1/hits_data?start=0&end=1973064434&group=day", + "raw": "{{endpoint}}/api/services/1/hits_data?start=0&end=5973064434&group=12h", "host": [ "{{endpoint}}" ], @@ -215,12 +471,12 @@ }, { "key": "end", - "value": "1973064434", + "value": "5973064434", "description": "End on time (unix timestamp)" }, { "key": "group", - "value": "day", + "value": "12h", "description": "Increment grouping (minute, hour, day)" } ] @@ -266,7 +522,7 @@ } ], "url": { - "raw": "{{endpoint}}/api/services/1/ping?start=0&end=1973064434&group=hour", + "raw": "{{endpoint}}/api/services/1/ping_data?start=0&end=9973064434&group=12h", "host": [ "{{endpoint}}" ], @@ -274,7 +530,7 @@ "api", "services", "1", - "ping" + "ping_data" ], "query": [ { @@ -284,12 +540,12 @@ }, { "key": "end", - "value": "1973064434", + "value": "9973064434", "description": "End on time (unix timestamp)" }, { "key": "group", - "value": "hour", + "value": "12h", "description": "Increment grouping (minute, hour, day)" } ] @@ -335,7 +591,7 @@ } ], "url": { - "raw": "{{endpoint}}/api/services/1/failure_data?start=0&end=1973064434&group=day", + "raw": "{{endpoint}}/api/services/1/failure_data?start=0&end=2973064434&group=12h", "host": [ "{{endpoint}}" ], @@ -352,11 +608,11 @@ }, { "key": "end", - "value": "1973064434" + "value": "2973064434" }, { "key": "group", - "value": "day" + "value": "12h" } ] }, @@ -401,7 +657,7 @@ } ], "url": { - "raw": "{{endpoint}}/api/services/1/failures", + "raw": "{{endpoint}}/api/services/1/failures?start=0&end=99999999999", "host": [ "{{endpoint}}" ], @@ -410,6 +666,16 @@ "services", "1", "failures" + ], + "query": [ + { + "key": "start", + "value": "0" + }, + { + "key": "end", + "value": "99999999999" + } ] }, "description": "Returns an array of failures for this service. It includes the error message, http status code, and the ping response time." @@ -458,7 +724,7 @@ } ], "url": { - "raw": "{{endpoint}}/api/services/1/hits", + "raw": "{{endpoint}}/api/services/1/hits?start=0&end=99999999999", "host": [ "{{endpoint}}" ], @@ -467,6 +733,16 @@ "services", "1", "hits" + ], + "query": [ + { + "key": "start", + "value": "0" + }, + { + "key": "end", + "value": "99999999999" + } ] }, "description": "Returns on array of all the successful hits for this service." @@ -509,17 +785,20 @@ ], "body": { "mode": "raw", - "raw": "[{\"service\":1,\"order\":1},{\"service\":2,\"order\":3},{\"service\":3,\"order\":4},{\"service\":4,\"order\":5}]" + "raw": "[{\"service\":1,\"order\":1},{\"service\":2,\"order\":3},{\"service\":3,\"order\":4},{\"service\":4,\"order\":5}]", + "options": { + "raw": {} + } }, "url": { - "raw": "{{endpoint}}/api/services/reorder", + "raw": "{{endpoint}}/api/reorder/services", "host": [ "{{endpoint}}" ], "path": [ "api", - "services", - "reorder" + "reorder", + "services" ] }, "description": "Reorder services in a specific order for the index page." @@ -695,8 +974,8 @@ "", "pm.test(\"Update Service\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.output.name).to.eql(\"Updated New Service\");", - " pm.expect(jsonData.output.domain).to.eql(\"https://google.com\");", + " pm.expect(jsonData.output.name).to.eql(\"Brand New Service\");", + " pm.expect(jsonData.output.domain).to.eql(\"https://google.net\");", " pm.expect(jsonData.output.type).to.eql(\"http\");", " pm.expect(jsonData.output.method).to.eql(\"GET\");", " pm.expect(jsonData.output.expected_status).to.eql(200);", @@ -726,7 +1005,10 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"Brand New Service\",\n \"domain\": \"https://google.net\",\n \"expected\": \"heyyyy\",\n \"expected_status\": 200,\n \"check_interval\": 20,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}" + "raw": "{\n \"name\": \"Brand New Service\",\n \"domain\": \"https://google.net\",\n \"expected\": \"heyyyy\",\n \"expected_status\": 200,\n \"check_interval\": 20,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}", + "options": { + "raw": {} + } }, "url": { "raw": "{{endpoint}}/api/services/{{service_id}}", @@ -790,6 +1072,55 @@ } ] }, + { + "name": "Delete Service Failures", + "event": [ + { + "listen": "test", + "script": { + "id": "dd4d721d-d874-448b-abc9-59c1afceb58e", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{endpoint}}/api/services/{{service_id}}/failures", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services", + "{{service_id}}", + "failures" + ] + }, + "description": "Delete all the service failures." + }, + "response": [] + }, { "name": "Delete Service", "event": [ @@ -886,55 +1217,6 @@ "body": "{\n \"status\": \"success\",\n \"type\": \"service\",\n \"method\": \"delete\",\n \"id\": 10,\n \"output\": {\n \"id\": 10,\n \"name\": \"Updated New Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 60,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0,\n \"allow_notifications\": false,\n \"created_at\": \"2018-12-10T11:31:47.535086-08:00\",\n \"updated_at\": \"2018-12-10T11:31:47.535184-08:00\",\n \"online\": true,\n \"latency\": 0.203382878,\n \"ping_time\": 0.001664491,\n \"online_24_hours\": 0,\n \"avg_response\": \"\",\n \"status_code\": 200,\n \"last_success\": \"2018-12-10T11:31:55.455091-08:00\"\n }\n}" } ] - }, - { - "name": "Delete Service Failures", - "event": [ - { - "listen": "test", - "script": { - "id": "dd4d721d-d874-448b-abc9-59c1afceb58e", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{endpoint}}/api/services/{{service_id}}/failures", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "services", - "{{service_id}}", - "failures" - ] - }, - "description": "Delete all the service failures." - }, - "response": [] } ], "description": "With the Statping API, you can add, remove, edit all your services fields from the API directly. This includes viewing Service chart data for latency/up-time, and even viewing a log of failures. ", @@ -969,8 +1251,7 @@ ] } } - ], - "protocolProfileBehavior": {} + ] }, { "name": "Groups", @@ -983,13 +1264,9 @@ "script": { "id": "d87f8a4e-7640-45b8-9d45-4f6e6f2463ee", "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});", - "", "pm.test(\"View All Groups\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.length).to.eql(3);", + " pm.expect(jsonData.length).to.eql(2);", "});" ], "type": "text/javascript" @@ -1304,14 +1581,14 @@ "raw": "[{\"group\":1,\"order\":1},{\"group\":2,\"order\":2}]" }, "url": { - "raw": "{{endpoint}}/api/groups/reorder", + "raw": "{{endpoint}}/api/reorder/groups", "host": [ "{{endpoint}}" ], "path": [ "api", - "groups", - "reorder" + "reorder", + "groups" ] }, "description": "Reorder services in a specific order for the index page." @@ -1448,8 +1725,7 @@ ] } } - ], - "protocolProfileBehavior": {} + ] }, { "name": "Users", @@ -1508,7 +1784,10 @@ "value": "admin", "type": "text" } - ] + ], + "options": { + "urlencoded": {} + } }, "url": { "raw": "{{endpoint}}/api/login", @@ -2002,8 +2281,7 @@ ] } } - ], - "protocolProfileBehavior": {} + ] }, { "name": "Notifiers", @@ -2022,7 +2300,7 @@ "", "pm.test(\"View All Notifiers\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.length).to.eql(9);", + " pm.expect(jsonData.length).to.eql(10);", "});" ], "type": "text/javascript" @@ -2035,7 +2313,7 @@ "bearer": [ { "key": "token", - "value": "e351393306ea245de5f9588cbe8627c74db007c6", + "value": "R1T2Cyugjjzw63km", "type": "string" } ] @@ -2086,14 +2364,14 @@ "method": "GET", "header": [], "url": { - "raw": "{{endpoint}}/api/notifier/mobile", + "raw": "{{endpoint}}/api/notifier/slack", "host": [ "{{endpoint}}" ], "path": [ "api", "notifier", - "mobile" + "slack" ] }, "description": "View a specific notifier and it's details." @@ -2108,16 +2386,10 @@ "script": { "id": "d714d71d-4d6a-4b2e-a6ea-16c34dec3041", "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});", - "", "pm.test(\"Update Notifier\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.output.method).to.eql(\"mobile\");", - " pm.expect(jsonData.output.enabled).to.eql(true);", - " pm.expect(jsonData.output.limits).to.eql(55);", - " pm.expect(jsonData.output.removeable).to.eql(false);", + " pm.expect(jsonData.status).to.eql(\"success\");", + " pm.expect(jsonData.output).to.eql(\"slack\");", "});" ], "type": "text/javascript" @@ -2146,17 +2418,17 @@ ], "body": { "mode": "raw", - "raw": "{\n \"method\": \"mobile\",\n \"var1\": \"ExponentPushToken[ToBadIWillError123456]\",\n \"enabled\": true,\n \"limits\": 55\n}" + "raw": "{\n \"method\": \"slack\",\n \"host\": \"https://hooks.slack.com/services/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\",\n \"enabled\": true,\n \"limits\": 55\n}" }, "url": { - "raw": "{{endpoint}}/api/notifier/mobile", + "raw": "{{endpoint}}/api/notifier/slack", "host": [ "{{endpoint}}" ], "path": [ "api", "notifier", - "mobile" + "slack" ] }, "description": "Update a notifier to change it's values." @@ -2164,7 +2436,7 @@ "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.", + "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/hunterlong/statping/wiki/Notifiers) on the Github repo.", "auth": { "type": "bearer", "bearer": [ @@ -2196,8 +2468,7 @@ ] } } - ], - "protocolProfileBehavior": {} + ] }, { "name": "Messages", @@ -2674,115 +2945,11 @@ ] } } - ], - "protocolProfileBehavior": {} + ] }, { "name": "Checkins", "item": [ - { - "name": "Create Checkin", - "event": [ - { - "listen": "test", - "script": { - "id": "af07a95b-1bf5-42c7-bd3f-a32f3ab2a264", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Create Checkin\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.status).to.eql(\"success\");", - " pm.expect(jsonData.type).to.eql(\"checkin\");", - " pm.expect(jsonData.output.name).to.eql(\"Server Checkin\");", - " pm.expect(jsonData.output.grace).to.eql(60);", - " pm.expect(jsonData.output.interval).to.eql(900);", - " var id = jsonData.output.api_key;", - " pm.globals.set(\"checkin_id\", id);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60\n}" - }, - "url": { - "raw": "{{endpoint}}/api/checkin", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "checkin" - ] - }, - "description": "Create a new Checkin." - }, - "response": [ - { - "name": "Create Checkin", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60\n}" - }, - "url": { - "raw": "{{endpoint}}/api/checkin", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "checkin" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Date", - "value": "Mon, 10 Dec 2018 19:34:10 GMT" - }, - { - "key": "Content-Length", - "value": "330" - } - ], - "cookie": [], - "body": "{\n \"status\": \"success\",\n \"type\": \"checkin\",\n \"method\": \"create\",\n \"id\": 5,\n \"output\": {\n \"id\": 5,\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60,\n \"api_key\": \"1emn9ha\",\n \"created_at\": \"2018-12-10T19:34:10.991372Z\",\n \"updated_at\": \"2018-12-10T19:34:10.991372Z\",\n \"failing\": false,\n \"last_hit\": \"0001-01-01T00:00:00Z\",\n \"hits\": null,\n \"failures\": null\n }\n}" - } - ] - }, { "name": "View All Checkin's", "event": [ @@ -2799,10 +2966,9 @@ " var jsonData = pm.response.json();", " var first = jsonData[0];", " var id = pm.globals.get(\"checkin_id\");", - " pm.expect(first.name).to.eql(\"Server Checkin\");", - " pm.expect(first.api_key).to.eql(id);", - " pm.expect(first.grace).to.eql(60);", - " pm.expect(first.interval).to.eql(900);", + " pm.expect(first.name).to.eql(\"Demo Checkin 1\");", + " pm.expect(first.grace).to.eql(300);", + " pm.expect(first.interval).to.eql(300);", "});" ], "type": "text/javascript" @@ -2863,6 +3029,119 @@ } ] }, + { + "name": "Create Checkin", + "event": [ + { + "listen": "test", + "script": { + "id": "af07a95b-1bf5-42c7-bd3f-a32f3ab2a264", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Create Checkin\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.status).to.eql(\"success\");", + " pm.expect(jsonData.type).to.eql(\"checkin\");", + " pm.expect(jsonData.output.name).to.eql(\"Server Checkin\");", + " pm.expect(jsonData.output.grace).to.eql(60);", + " pm.expect(jsonData.output.interval).to.eql(900);", + " var id = jsonData.output.api_key;", + " pm.globals.set(\"checkin_id\", id);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60\n}" + }, + "url": { + "raw": "{{endpoint}}/api/checkins", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "checkins" + ] + }, + "description": "Create a new Checkin." + }, + "response": [ + { + "name": "Create Checkin", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60\n}" + }, + "url": { + "raw": "{{endpoint}}/api/checkin", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "checkin" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Mon, 10 Dec 2018 19:34:10 GMT" + }, + { + "key": "Content-Length", + "value": "330" + } + ], + "cookie": [], + "body": "{\n \"status\": \"success\",\n \"type\": \"checkin\",\n \"method\": \"create\",\n \"id\": 5,\n \"output\": {\n \"id\": 5,\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60,\n \"api_key\": \"1emn9ha\",\n \"created_at\": \"2018-12-10T19:34:10.991372Z\",\n \"updated_at\": \"2018-12-10T19:34:10.991372Z\",\n \"failing\": false,\n \"last_hit\": \"0001-01-01T00:00:00Z\",\n \"hits\": null,\n \"failures\": null\n }\n}" + } + ] + }, { "name": "Run Checkin", "event": [ @@ -2878,7 +3157,6 @@ "pm.test(\"Hit the Checkin API Endpoint\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData.status).to.eql(\"success\");", - " pm.expect(jsonData.type).to.eql(\"checkin_hit\");", " pm.expect(jsonData.method).to.eql(\"update\");", "});" ], @@ -2969,13 +3247,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{endpoint}}/api/checkin/{{checkin_id}}", + "raw": "{{endpoint}}/api/checkins/{{checkin_id}}", "host": [ "{{endpoint}}" ], "path": [ "api", - "checkin", + "checkins", "{{checkin_id}}" ] }, @@ -3047,6 +3325,16 @@ } ], "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, "method": "DELETE", "header": [], "body": { @@ -3054,13 +3342,13 @@ "raw": "" }, "url": { - "raw": "{{endpoint}}/api/checkin/{{checkin_id}}", + "raw": "{{endpoint}}/api/checkins/{{checkin_id}}", "host": [ "{{endpoint}}" ], "path": [ "api", - "checkin", + "checkins", "{{checkin_id}}" ] }, @@ -3133,342 +3421,7 @@ ] } } - ], - "protocolProfileBehavior": {} - }, - { - "name": "Miscellaneous", - "item": [ - { - "name": "Statping Setup", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Check Core API Route\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.description).to.eql(\"Statping Monitoring Sample Data\");", - " pm.expect(jsonData.using_cdn).to.eql(false);", - " pm.expect(jsonData.config.connection).to.eql(\"sqlite\");", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/x-www-form-urlencoded", - "type": "text" - } - ], - "body": { - "mode": "urlencoded", - "urlencoded": [ - { - "key": "db_host", - "value": "localhost", - "type": "text" - }, - { - "key": "db_user", - "value": "root", - "type": "text" - }, - { - "key": "db_password", - "value": "password123", - "type": "text" - }, - { - "key": "db_database", - "value": "statping", - "type": "text" - }, - { - "key": "db_connection", - "value": "sqlite", - "type": "text" - }, - { - "key": "db_port", - "value": "3306", - "type": "text" - }, - { - "key": "project", - "value": "Statping Demo", - "type": "text" - }, - { - "key": "description", - "value": "Statping setup from POST", - "type": "text" - }, - { - "key": "username", - "value": "admin", - "type": "text" - }, - { - "key": "password", - "value": "admin", - "type": "text" - }, - { - "key": "domain", - "value": "http://localhost:8080", - "type": "text" - }, - { - "key": "email", - "value": "info@domain.com", - "type": "text" - }, - { - "key": "sample_data", - "value": "true", - "type": "text" - } - ] - }, - "url": { - "raw": "{{endpoint}}/api/setup", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "setup" - ] - }, - "description": "The root API endpoint to view basic Statping configuration including Name, URL, database type, and other useful fields." - }, - "response": [] - }, - { - "name": "Statping Details", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Check Core API Route\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.description).to.eql(\"Statping Monitoring Sample Data\");", - " pm.expect(jsonData.using_cdn).to.eql(false);", - " pm.expect(jsonData.config.connection).to.eql(\"sqlite\");", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/api", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api" - ] - }, - "description": "The root API endpoint to view basic Statping configuration including Name, URL, database type, and other useful fields." - }, - "response": [] - }, - { - "name": "Statping Clear Cache", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/api/clear_cache", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "clear_cache" - ] - }, - "description": "This endpoint will clear all the cache files in your Statping instance. This includes chart data and service views." - }, - "response": [] - }, - { - "name": "Statping Reset API Tokens", - "event": [ - { - "listen": "test", - "script": { - "id": "08b8f487-2318-44b9-bdb8-f1f1041e9462", - "exec": [ - "pm.test(\"Response is ok\", function () {", - " pm.response.to.have.status(200);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{endpoint}}/api/renew", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "renew" - ] - }, - "description": "Reset your root API Key and Secret values to brand new values." - }, - "response": [] - } - ], - "description": "This is for Statping's miscellaneous API endpoints that aren't a part of another category.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{api_key}}", - "type": "string" - } - ] - }, - "event": [ - { - "listen": "prerequest", - "script": { - "id": "883519e8-7c7d-49c0-9812-d988d0179907", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "7a0738e6-2fc4-45cb-9f1a-1cd57fb76b66", - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "protocolProfileBehavior": {} + ] } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "", - "type": "string" - } - ] - }, - "event": [ - { - "listen": "prerequest", - "script": { - "id": "4eb22861-cca3-4592-a1a4-4d01bc62ee74", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "cac1a6f2-2dc3-4b09-9849-3d5aab919244", - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "protocolProfileBehavior": {} + ] } diff --git a/dev/postman_environment.json b/dev/postman_environment.json index 66f82a87..ad4333ca 100644 --- a/dev/postman_environment.json +++ b/dev/postman_environment.json @@ -7,9 +7,15 @@ "value": "http://127.0.0.1:8080", "description": "", "enabled": true + }, + { + "key": "api_key", + "value": "demosecret123", + "description": "", + "enabled": true } ], "_postman_variable_scope": "environment", "_postman_exported_at": "2018-11-17T16:55:15.031Z", "_postman_exported_using": "Postman/6.5.2" -} \ No newline at end of file +} diff --git a/dev/pwd-stack.yml b/dev/pwd-stack.yml new file mode 100644 index 00000000..dcbe254d --- /dev/null +++ b/dev/pwd-stack.yml @@ -0,0 +1,20 @@ +version: '3.1' + +services: + + statping: + hostname: statping + image: statping/statping + ports: + - 8080:8080 + environment: + PORT: 8080 + SERVICES: '[{"name": "Local Statping", "type": "http", "domain": "http://localhost:8585", "interval": 30}]' + DB_CONN: sqlite + API_KEY: exampleapikey + API_SECRET: exampleapisecret + NAME: Statping on SQLite + DOMAIN: http://localhost:8080 + DESCRIPTION: This is a dev environment on SQLite! + ADMIN_USER: admin + ADMIN_PASS: admin diff --git a/frontend/package.json b/frontend/package.json index b080a5a6..f6bd23ee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ "cypress:open": "cypress open", "cypress:test": "cypress run --browser chrome --record false --key $CYPRESS_KEY", "test": "start-server-and-test start http://localhost:8080/api cypress:test", - "start": "statping -port 8080" + "start": "statping --port 8080 > /dev/null" }, "dependencies": { "@fortawesome/fontawesome-free-solid": "^5.1.0-3", diff --git a/frontend/src/API.js b/frontend/src/API.js index 5b431e0a..0ae562e2 100644 --- a/frontend/src/API.js +++ b/frontend/src/API.js @@ -12,6 +12,11 @@ class Api { } + async oauth() { + const oauth = axios.get('api/oauth').then(response => (response.data)) + return oauth + } + async core() { const core = axios.get('api').then(response => (response.data)) if (core.allow_reports) { @@ -198,7 +203,7 @@ class Api { } async logs() { - return axios.get('api/logs').then(response => (response.data)) + return axios.get('api/logs').then(response => (response.data)) || [] } async logs_last() { diff --git a/frontend/src/components/Elements/LoadButton.vue b/frontend/src/components/Elements/LoadButton.vue index 75c5152a..ce36c9d5 100644 --- a/frontend/src/components/Elements/LoadButton.vue +++ b/frontend/src/components/Elements/LoadButton.vue @@ -1,5 +1,5 @@ @@ -15,10 +15,6 @@ type: String, required: true }, - class: { - type: String, - default: "btn-primary" - }, disabled: { type: Boolean, default: false diff --git a/frontend/src/components/Service/Analytics.vue b/frontend/src/components/Service/Analytics.vue index b1eed505..7172a7fe 100644 --- a/frontend/src/components/Service/Analytics.vue +++ b/frontend/src/components/Service/Analytics.vue @@ -13,7 +13,6 @@ diff --git a/frontend/src/components/Service/ServiceInfo.vue b/frontend/src/components/Service/ServiceInfo.vue index 88e5a81f..d12b80be 100644 --- a/frontend/src/components/Service/ServiceInfo.vue +++ b/frontend/src/components/Service/ServiceInfo.vue @@ -104,7 +104,6 @@ import ServiceFailures from './ServiceFailures'; import ServiceSparkLine from "./ServiceSparkLine"; import Api from "../../API"; - import StatsGen from "./StatsGen"; export default { name: 'ServiceInfo', @@ -113,7 +112,6 @@ ServiceFailures, FormIncident, FormMessage, - StatsGen, ServiceSparkLine }, props: { diff --git a/frontend/src/components/Service/StatsGen.vue b/frontend/src/components/Service/StatsGen.vue deleted file mode 100644 index ecaf061e..00000000 --- a/frontend/src/components/Service/StatsGen.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - diff --git a/frontend/src/forms/Login.vue b/frontend/src/forms/Login.vue index f2adb46e..7b02ba47 100644 --- a/frontend/src/forms/Login.vue +++ b/frontend/src/forms/Login.vue @@ -1,4 +1,5 @@ diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index f7a7a91f..bb29c725 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -1,13 +1,14 @@