diff --git a/.dockerignore b/.dockerignore index b77cda83..ac8bba96 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,7 @@ .github .idea logs +certs *.log build assets diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7522267e..3777601f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,4 @@ github: hunterlong patreon: statping -custom: ['https://www.nfoservers.com/donate.pl?force_recipient=1&recipient=info%40socialeck.com', 'https://opencollective.com/statping', 'https://www.buymeacoffee.com/hunterlong'] +open_collective: statping +custom: ['https://www.nfoservers.com/donate.pl?force_recipient=1&recipient=info%40socialeck.com', 'https://www.buymeacoffee.com/hunterlong'] diff --git a/.github/workflows/dev.yml b/.github/workflows/development.yml similarity index 52% rename from .github/workflows/dev.yml rename to .github/workflows/development.yml index 7ad19d81..cdf00ff4 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/development.yml @@ -1,4 +1,4 @@ -name: Dev Release +name: Development Build on: push: branches: @@ -7,40 +7,46 @@ on: - '**.md' jobs: - compile: + + frontend: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.x - uses: actions/setup-node@v1 with: - node-version: '10.x' - - uses: actions/checkout@v2 + node-version: 12.18.2 - - name: Add GOBIN to PATH + - name: Set Version 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 + run: npm install -g yarn sass cross-env mjml - 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 + env: + VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} + MJML_APP: ${{ secrets.MJML_APP }} + MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: make clean generate compile - name: Upload Compiled Frontend (rice-box.go) uses: actions/upload-artifact@v1 @@ -48,10 +54,145 @@ jobs: name: static-rice-box path: ./source - test: - needs: compile - runs-on: ubuntu-latest + - name: Configure AWS credentials for Asset uploads + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + - name: Upload Static Assets to S3 + run: | + tar -czvf source.tar.gz source/ + aws s3 cp source.tar.gz s3://assets.statping.com/commit/${{ github.sha }}/ + rm -rf source.tar.gz + + build: + needs: frontend + runs-on: ubuntu-latest + strategy: + matrix: + platform: [linux] + arch: [386, amd64, arm-7, arm-6, arm64] + include: + - platform: darwin + arch: amd64 + - platform: windows + arch: amd64 + + steps: + - uses: actions/checkout@v2 + + - name: Download Compiled Frontend (rice-box.go) + uses: actions/download-artifact@v1 + with: + name: static-rice-box + path: ./source + + - name: Configure AWS credentials for Asset uploads + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + + - name: Add GOBIN to PATH + run: | + echo ::set-env name=VERSION::$(cat version.txt) + shell: bash + + - name: Set Linux Build Flags + if: matrix.platform == 'linux' + run: | + echo ::set-env name=BUILD_FLAGS::'-extldflags -static' + echo ::set-env name=XGO_TAGS::'netgo,osusergo,linux,sqlite_omit_load_extension' + shell: bash + + - name: Set Darwin Build Flags + if: matrix.platform == 'darwin' + run: echo ::set-env name=XGO_TAGS::'netgo,osusergo,darwin,sqlite_omit_load_extension' + shell: bash + + - name: Set Windows Build Flags + if: matrix.platform == 'windows' + run: | + echo ::set-env name=BUILD_FLAGS::'-extldflags -static' + echo ::set-env name=XGO_TAGS::'netgo,osusergo,sqlite_omit_load_extension' + shell: bash + + - name: Build ${{ matrix.platform }}/${{ matrix.arch }} + uses: crazy-max/ghaction-xgo@v1 + env: + VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} + with: + xgo_version: latest + go_version: 1.15.x + dest: build + prefix: statping + targets: ${{ matrix.platform }}/${{ matrix.arch }} + v: false + x: false + pkg: cmd + buildmode: pie + tags: ${{ env.XGO_TAGS }} + ldflags: -s -w -X main.VERSION=${{ env.VERSION }} -X main.COMMIT=${{ env.COMMIT }} ${{ env.BUILD_FLAGS }} + + - name: Compress Linux Builds + if: matrix.platform == 'linux' + run: | + cd build + mv statping-linux-${{ matrix.arch }} statping + chmod +x statping + tar -czvf statping-linux-${{ matrix.arch }}.tar.gz statping + rm -rf statping + echo ::set-env name=compressed::statping-linux-${{ matrix.arch }}.tar.gz + + - name: Compress Windows Builds + if: matrix.platform == 'windows' + run: | + cd build + mv statping-windows-4.0-${{ matrix.arch }}.exe statping.exe + chmod +x statping.exe + zip statping-windows-${{ matrix.arch }}.zip statping.exe + rm -rf statping.exe + echo ::set-env name=compressed::statping-windows-${{ matrix.arch }}.zip + + - name: Compress Darwin Builds + if: matrix.platform == 'darwin' + run: | + cd build + mv statping-darwin-10.6-${{ matrix.arch }} statping + chmod +x statping + tar -czvf statping-darwin-${{ matrix.arch }}.tar.gz statping + rm -rf statping + echo ::set-env name=compressed::statping-darwin-${{ matrix.arch }}.tar.gz + + - name: Upload Compiled Statping Binary + uses: actions/upload-artifact@v1 + with: + name: statping-${{ matrix.platform }}-${{ matrix.arch }} + path: ./build + + - name: Upload Releases + id: upload-assets + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} + with: + tag_name: dev-v${{ env.VERSION }} + draft: true + prerelease: true + files: build/${{ env.compressed }} + + - name: Upload Compiled Binaries to S3 + run: | + aws s3 cp build/${{ env.compressed }} s3://assets.statping.com/commit/${{ github.sha }}/ + + test: + needs: frontend + runs-on: ubuntu-latest services: postgres: image: postgres:10.8 @@ -73,13 +214,13 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: + - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.x - uses: actions/setup-node@v1 with: - node-version: '10.x' - - uses: actions/checkout@v2 + node-version: 12.18.2 - name: Install Global Dependencies run: npm install -g yarn sass newman cross-env wait-on @sentry/cli @@ -87,7 +228,7 @@ jobs: - name: Setting ENV's run: | echo "::add-path::$(go env GOPATH)/bin" - echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin" + echo "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -100,6 +241,7 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build certs chmod +x statping @@ -111,11 +253,13 @@ jobs: gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./... env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} DB_CONN: sqlite3 STATPING_DIR: ${{ github.workspace }} API_SECRET: demopassword123 DISABLE_LOGS: false ALLOW_REPORTS: true + SAMPLE_DATA: true COVERALLS: ${{ secrets.COVERALLS }} DISCORD_URL: ${{ secrets.DISCORD_URL }} EMAIL_HOST: ${{ secrets.EMAIL_HOST }} @@ -136,6 +280,12 @@ jobs: TWILIO_FROM: ${{ secrets.TWILIO_FROM }} TWILIO_TO: ${{ secrets.TWILIO_TO }} TEST_EMAIL: ${{ secrets.TEST_EMAIL }} + GOTIFY_URL: ${{ secrets.GOTIFY_URL }} + GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }} + SNS_TOKEN: ${{ secrets.SNS_TOKEN }} + SNS_SECRET: ${{ secrets.SNS_SECRET }} + SNS_REGION: ${{ secrets.SNS_REGION }} + SNS_TOPIC: ${{ secrets.SNS_TOPIC }} - name: Coveralls Testing Coverage run: | @@ -145,18 +295,18 @@ jobs: COVERALLS: ${{ secrets.COVERALLS }} test-postman-sqlite: - needs: compile + needs: frontend runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.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 "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -169,16 +319,15 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build chmod +x statping mv statping $(go env GOPATH)/bin/ - - name: Run Statping run: | API_SECRET=demosecret123 statping --port=8585 > /dev/null & sleep 5 - - name: Postman SQLite Tests uses: matt-ball/newman-action@master with: @@ -189,7 +338,7 @@ jobs: delayRequest: 600 test-postman-mysql: - needs: compile + needs: frontend runs-on: ubuntu-latest services: @@ -206,12 +355,12 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.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 "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -224,16 +373,17 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} + MJML_APP: ${{ secrets.MJML_APP }} + MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }} run: | make build chmod +x statping mv statping $(go env GOPATH)/bin/ - - name: Run Statping run: | API_SECRET=demosecret123 statping --port=8585 > /dev/null & sleep 5 - - name: Postman MySQL Tests uses: matt-ball/newman-action@master with: @@ -244,7 +394,7 @@ jobs: delayRequest: 600 test-postman-postgres: - needs: compile + needs: frontend runs-on: ubuntu-latest services: @@ -262,12 +412,12 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.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 "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -280,16 +430,15 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build chmod +x statping mv statping $(go env GOPATH)/bin/ - - name: Run Statping run: | API_SECRET=demosecret123 statping --port=8585 > /dev/null & sleep 5 - - name: Postman Postgres Tests uses: matt-ball/newman-action@master with: @@ -300,7 +449,7 @@ jobs: delayRequest: 600 docker-release: - needs: [test, test-postman-sqlite, test-postman-mysql, test-postman-postgres] + needs: [test, build, test-postman-sqlite, test-postman-mysql, test-postman-postgres] runs-on: ubuntu-latest steps: - name: Checkout Statping Repo @@ -310,25 +459,47 @@ jobs: 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: Set up Docker Buildx + uses: crazy-max/ghaction-docker-buildx@v3 - - name: Dev Docker Image - uses: elgohr/Publish-Docker-Github-Action@master + - name: Docker Login env: - VERSION: ${{ env.VERSION }} - ARCH: amd64 - DOCKER_CLI_EXPERIMENTAL: enabled + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin + + - name: Cache Docker layers + uses: actions/cache@v2 + id: buildx-docker-master with: - name: statping/statping - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - dockerfile: Dockerfile - tags: "dev" - buildargs: VERSION,ARCH + path: /tmp/.buildx-cache + key: buildx-docker-master + restore-keys: | + buildx-docker-master + - name: Docker Build :base + run: make buildx-base + + - name: Docker Build :dev + run: make buildx-dev + + sentry-release: + needs: [test, test-postman-sqlite, test-postman-postgres, test-postman-mysql] + runs-on: ubuntu-latest + steps: + - name: Checkout Statping Repo + uses: actions/checkout@v2 + + - name: Setup Sentry CLI + uses: mathrix-education/setup-sentry-cli@master + with: + version: latest + url: ${{ secrets.SENTRY_URL }} + token: ${{ secrets.SENTRY_AUTH_TOKEN }} + organization: statping + + - name: Setting ENV's + run: echo ::set-env name=VERSION::$(cat version.txt) + shell: bash + + - name: Sentry Release + run: make sentry-release diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 38fbb862..002ba915 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -1,4 +1,4 @@ -name: Master Release +name: Master Build on: push: branches: @@ -7,40 +7,46 @@ on: - '**.md' jobs: - compile: + + frontend: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.x - uses: actions/setup-node@v1 with: - node-version: '10.x' - - uses: actions/checkout@v2 + node-version: 12.18.2 - - name: Add GOBIN to PATH + - name: Set Version 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 + run: npm install -g yarn sass cross-env mjml - 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 + env: + VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} + MJML_APP: ${{ secrets.MJML_APP }} + MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: make clean generate compile - name: Upload Compiled Frontend (rice-box.go) uses: actions/upload-artifact@v1 @@ -48,10 +54,145 @@ jobs: name: static-rice-box path: ./source - test: - needs: compile - runs-on: ubuntu-latest + - name: Configure AWS credentials for Asset uploads + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + - name: Upload Static Assets to S3 + run: | + tar -czvf source.tar.gz source/ + aws s3 cp source.tar.gz s3://assets.statping.com/commit/${{ github.sha }}/ + rm -rf source.tar.gz + + build: + needs: frontend + runs-on: ubuntu-latest + strategy: + matrix: + platform: [linux] + arch: [386, amd64, arm-7, arm-6, arm64] + include: + - platform: darwin + arch: amd64 + - platform: windows + arch: amd64 + + steps: + - uses: actions/checkout@v2 + + - name: Download Compiled Frontend (rice-box.go) + uses: actions/download-artifact@v1 + with: + name: static-rice-box + path: ./source + + - name: Configure AWS credentials for Asset uploads + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + + - name: Add GOBIN to PATH + run: | + echo ::set-env name=VERSION::$(cat version.txt) + shell: bash + + - name: Set Linux Build Flags + if: matrix.platform == 'linux' + run: | + echo ::set-env name=BUILD_FLAGS::'-extldflags -static' + echo ::set-env name=XGO_TAGS::'netgo,osusergo,linux,sqlite_omit_load_extension' + shell: bash + + - name: Set Darwin Build Flags + if: matrix.platform == 'darwin' + run: echo ::set-env name=XGO_TAGS::'netgo,osusergo,darwin,sqlite_omit_load_extension' + shell: bash + + - name: Set Windows Build Flags + if: matrix.platform == 'windows' + run: | + echo ::set-env name=BUILD_FLAGS::'-extldflags -static' + echo ::set-env name=XGO_TAGS::'netgo,osusergo,sqlite_omit_load_extension' + shell: bash + + - name: Build ${{ matrix.platform }}/${{ matrix.arch }} + uses: crazy-max/ghaction-xgo@v1 + env: + VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} + with: + xgo_version: latest + go_version: 1.15.x + dest: build + prefix: statping + targets: ${{ matrix.platform }}/${{ matrix.arch }} + v: false + x: false + pkg: cmd + buildmode: pie + tags: ${{ env.XGO_TAGS }} + ldflags: -s -w -X main.VERSION=${{ env.VERSION }} -X main.COMMIT=${{ env.COMMIT }} ${{ env.BUILD_FLAGS }} + + - name: Compress Linux Builds + if: matrix.platform == 'linux' + run: | + cd build + mv statping-linux-${{ matrix.arch }} statping + chmod +x statping + tar -czvf statping-linux-${{ matrix.arch }}.tar.gz statping + rm -rf statping + echo ::set-env name=compressed::statping-linux-${{ matrix.arch }}.tar.gz + + - name: Compress Windows Builds + if: matrix.platform == 'windows' + run: | + cd build + mv statping-windows-4.0-${{ matrix.arch }}.exe statping.exe + chmod +x statping.exe + zip statping-windows-${{ matrix.arch }}.zip statping.exe + rm -rf statping.exe + echo ::set-env name=compressed::statping-windows-${{ matrix.arch }}.zip + + - name: Compress Darwin Builds + if: matrix.platform == 'darwin' + run: | + cd build + mv statping-darwin-10.6-${{ matrix.arch }} statping + chmod +x statping + tar -czvf statping-darwin-${{ matrix.arch }}.tar.gz statping + rm -rf statping + echo ::set-env name=compressed::statping-darwin-${{ matrix.arch }}.tar.gz + + - name: Upload Compiled Statping Binary + uses: actions/upload-artifact@v1 + with: + name: statping-${{ matrix.platform }}-${{ matrix.arch }} + path: ./build + + - name: Upload Releases + id: upload-assets + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} + with: + tag_name: v${{ env.VERSION }} + draft: true + prerelease: true + files: build/${{ env.compressed }} + + - name: Upload Compiled Binaries to S3 + run: | + aws s3 cp build/${{ env.compressed }} s3://assets.statping.com/commit/${{ github.sha }}/ + + test: + needs: frontend + runs-on: ubuntu-latest services: postgres: image: postgres:10.8 @@ -73,13 +214,13 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: + - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.x - uses: actions/setup-node@v1 with: - node-version: '10.x' - - uses: actions/checkout@v2 + node-version: 12.18.2 - name: Install Global Dependencies run: npm install -g yarn sass newman cross-env wait-on @sentry/cli @@ -87,7 +228,7 @@ jobs: - name: Setting ENV's run: | echo "::add-path::$(go env GOPATH)/bin" - echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin" + echo "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -100,6 +241,7 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build certs chmod +x statping @@ -111,11 +253,13 @@ jobs: gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./... env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} DB_CONN: sqlite3 STATPING_DIR: ${{ github.workspace }} API_SECRET: demopassword123 DISABLE_LOGS: false ALLOW_REPORTS: true + SAMPLE_DATA: true COVERALLS: ${{ secrets.COVERALLS }} DISCORD_URL: ${{ secrets.DISCORD_URL }} EMAIL_HOST: ${{ secrets.EMAIL_HOST }} @@ -136,6 +280,12 @@ jobs: TWILIO_FROM: ${{ secrets.TWILIO_FROM }} TWILIO_TO: ${{ secrets.TWILIO_TO }} TEST_EMAIL: ${{ secrets.TEST_EMAIL }} + GOTIFY_URL: ${{ secrets.GOTIFY_URL }} + GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }} + SNS_TOKEN: ${{ secrets.SNS_TOKEN }} + SNS_SECRET: ${{ secrets.SNS_SECRET }} + SNS_REGION: ${{ secrets.SNS_REGION }} + SNS_TOPIC: ${{ secrets.SNS_TOPIC }} - name: Coveralls Testing Coverage run: | @@ -145,18 +295,18 @@ jobs: COVERALLS: ${{ secrets.COVERALLS }} test-postman-sqlite: - needs: compile + needs: frontend runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.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 "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -169,16 +319,15 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build chmod +x statping mv statping $(go env GOPATH)/bin/ - - name: Run Statping run: | API_SECRET=demosecret123 statping --port=8585 > /dev/null & sleep 5 - - name: Postman SQLite Tests uses: matt-ball/newman-action@master with: @@ -189,7 +338,7 @@ jobs: delayRequest: 600 test-postman-mysql: - needs: compile + needs: frontend runs-on: ubuntu-latest services: @@ -206,12 +355,12 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.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 "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -224,16 +373,17 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} + MJML_APP: ${{ secrets.MJML_APP }} + MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }} run: | make build chmod +x statping mv statping $(go env GOPATH)/bin/ - - name: Run Statping run: | API_SECRET=demosecret123 statping --port=8585 > /dev/null & sleep 5 - - name: Postman MySQL Tests uses: matt-ball/newman-action@master with: @@ -244,7 +394,7 @@ jobs: delayRequest: 600 test-postman-postgres: - needs: compile + needs: frontend runs-on: ubuntu-latest services: @@ -262,12 +412,12 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.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 "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -280,16 +430,15 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build chmod +x statping mv statping $(go env GOPATH)/bin/ - - name: Run Statping run: | API_SECRET=demosecret123 statping --port=8585 > /dev/null & sleep 5 - - name: Postman Postgres Tests uses: matt-ball/newman-action@master with: @@ -299,110 +448,8 @@ jobs: timeoutRequest: 30000 delayRequest: 600 - build-binaries: - needs: compile - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v2 - with: - go-version: '1.14.2' - - uses: actions/checkout@v2 - - - name: Install cross compiling libraries - run: | - sudo apt-get update - sudo apt-get install -y automake autogen build-essential ca-certificates libsqlite3-dev \ - gcc-5-arm-linux-gnueabi g++-5-arm-linux-gnueabi libc6-dev-armel-cross linux-headers-generic \ - gcc-5-arm-linux-gnueabihf g++-5-arm-linux-gnueabihf libc6-dev-armhf-cross \ - gcc-5-aarch64-linux-gnu g++-5-aarch64-linux-gnu libc6-dev-arm64-cross \ - gcc-5-mips-linux-gnu g++-5-mips-linux-gnu libc6-dev-mips-cross \ - gcc-5-mipsel-linux-gnu g++-5-mipsel-linux-gnu libc6-dev-mipsel-cross \ - gcc-5-mips64-linux-gnuabi64 g++-5-mips64-linux-gnuabi64 libc6-dev-mips64-cross \ - gcc-5-mips64el-linux-gnuabi64 g++-5-mips64el-linux-gnuabi64 libc6-dev-mips64el-cross \ - gcc-5-multilib g++-5-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \ - gcc-6-arm-linux-gnueabi g++-6-arm-linux-gnueabi libc6-dev-armel-cross \ - gcc-6-arm-linux-gnueabihf g++-6-arm-linux-gnueabihf libc6-dev-armhf-cross \ - gcc-6-aarch64-linux-gnu g++-6-aarch64-linux-gnu libc6-dev-arm64-cross \ - gcc-6-mips-linux-gnu g++-6-mips-linux-gnu libc6-dev-mips-cross \ - gcc-6-mipsel-linux-gnu g++-6-mipsel-linux-gnu libc6-dev-mipsel-cross \ - gcc-6-mips64-linux-gnuabi64 g++-6-mips64-linux-gnuabi64 libc6-dev-mips64-cross \ - gcc-6-mips64el-linux-gnuabi64 g++-6-mips64el-linux-gnuabi64 libc6-dev-mips64el-cross \ - gcc-6-multilib gcc-7-multilib g++-6-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \ - libtool libxml2-dev uuid-dev libssl-dev swig openjdk-8-jdk pkg-config patch \ - make xz-utils cpio wget zip unzip p7zip git mercurial bzr texinfo help2man cmake --no-install-recommends - sudo ln -s /usr/include/asm-generic/ /usr/include/asm - - - 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 - run: | - 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-folders build-linux build-linux-arm build-darwin build-win compress-folders - - - name: Upload Builds - uses: actions/upload-artifact@v1 - with: - name: builds - path: ./build - - upload-release: - needs: [test, test-postman-sqlite, test-postman-mysql, test-postman-postgres, build-binaries] - 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 Builds - uses: actions/download-artifact@v1 - with: - name: builds - path: ./builds - - - name: Upload Releases - id: upload-assets - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ env.VERSION }} - with: - tag_name: v${{ env.VERSION }} - draft: false - prerelease: true - files: | - builds/statping-linux-386.tar.gz - builds/statping-linux-amd64.tar.gz - builds/statping-linux-arm.tar.gz - builds/statping-linux-arm64.tar.gz - builds/statping-darwin-amd64.tar.gz - builds/statping-darwin-386.tar.gz - builds/statping-windows-386.zip - builds/statping-windows-amd64.zip - builds/statping-windows-arm.zip - docker-release: - needs: upload-release + needs: [build, test, test-postman-sqlite, test-postman-postgres, test-postman-mysql] runs-on: ubuntu-latest steps: - name: Checkout Statping Repo @@ -412,64 +459,53 @@ jobs: 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: Set up Docker Buildx + uses: crazy-max/ghaction-docker-buildx@v3 - - name: Latest/Version Docker Image - uses: elgohr/Publish-Docker-Github-Action@master + - name: Docker Login env: - VERSION: ${{ env.VERSION }} - ARCH: amd64 - DOCKER_CLI_EXPERIMENTAL: enabled + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin + + - name: Cache Docker layers + uses: actions/cache@v2 + id: buildx-docker with: - name: statping/statping - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - dockerfile: Dockerfile - tags: "latest,v${{ env.VERSION }}" - buildargs: VERSION,ARCH + path: /tmp/.buildx-cache + key: buildx-docker + restore-keys: | + buildx-docker + - name: Docker Build :base + run: make buildx-base + + - name: Docker Build :lastest + run: make buildx-latest sentry-release: - needs: upload-release + needs: docker-release runs-on: ubuntu-latest steps: - name: Checkout Statping Repo uses: actions/checkout@v2 + - name: Setup Sentry CLI + uses: mathrix-education/setup-sentry-cli@master + with: + version: latest + url: ${{ secrets.SENTRY_URL }} + token: ${{ secrets.SENTRY_AUTH_TOKEN }} + organization: statping + - 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 + - name: Sentry Release + run: make sentry-release homebrew-release: - needs: upload-release + needs: docker-release runs-on: ubuntu-latest steps: - name: Checkout Statping Repo @@ -486,7 +522,7 @@ jobs: run: make publish-homebrew slack-update: - needs: upload-release + needs: docker-release runs-on: ubuntu-latest steps: - name: Checkout Statping Repo @@ -497,7 +533,7 @@ jobs: shell: bash - name: Slack Notification - uses: rtCamp/action-slack-notify@v2.0.0 + uses: rtCamp/action-slack-notify@master env: SLACK_WEBHOOK: ${{ secrets.SLACK_URL }} SLACK_CHANNEL: dev diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7602ac04..239851c8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,9 +5,14 @@ on: - '*' # matches every branch - '*/*' # matches every branch containing a single '/' - '!master' # excludes master + - '!dev' # excludes dev pull_request: branches: - - dev + - '*' # matches every branch + - '*/*' # matches every branch containing a single '/' + - '!master' # excludes master + - '!dev' # excludes dev + jobs: compile: @@ -18,7 +23,7 @@ jobs: go-version: '1.14.2' - uses: actions/setup-node@v1 with: - node-version: '10.x' + node-version: '12.18.2' - uses: actions/checkout@v2 - name: Add GOBIN to PATH @@ -78,10 +83,10 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: '1.14.2' + go-version: 1.15.x - uses: actions/setup-node@v1 with: - node-version: '10.x' + node-version: 12.18.2 - uses: actions/checkout@v2 - name: Install Global Dependencies @@ -92,7 +97,7 @@ jobs: - name: Setting ENV's run: | echo "::add-path::$(go env GOPATH)/bin" - echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin" + echo "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -115,6 +120,7 @@ jobs: SASS=`which sass` gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./... env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} DB_CONN: sqlite3 STATPING_DIR: ${{ github.workspace }} API_SECRET: demopassword123 @@ -134,7 +140,7 @@ jobs: - name: Setting ENV's run: | echo "::add-path::$(go env GOPATH)/bin" - echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin" + echo "::add-path::/opt/hostedtoolcache/node/12.18.2/x64/bin" echo ::set-env name=VERSION::$(cat version.txt) shell: bash @@ -147,6 +153,7 @@ jobs: - name: Install Statping env: VERSION: ${{ env.VERSION }} + COMMIT: ${{ github.sha }} run: | make build chmod +x statping diff --git a/.gitignore b/.gitignore index a68fd1af..ccdb9205 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ snap prime stage parts +assets_backup +certs releases core/rice-box.go config.yml @@ -37,3 +39,4 @@ tmp /frontend/cypress/screenshots/ /frontend/cypress/videos/ services.yml +statping.wiki diff --git a/CHANGELOG.md b/CHANGELOG.md index cf99f370..e6d693af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,97 @@ +# 0.90.69 (09-18-2020) +- Fixed issue with service view not loading. #808 #811 #800 + +# 0.90.68 (09-17-2020) +- Added DB_DSN env for mysql, postgres or sqlite DSN database connection string +- Added READ_ONLY env for a read only connection to the database +- Added Custom OAuth OpenID toggle switch in settings (appends 'openid' in scope) +- Fixed Custom OAuth response_type issue +- Added Configs tab in Settings to edit the config.yml from frontend + +# 0.90.67 (09-14-2020) +- Modified core settings to update config.yml on save +- Modified Theme Editor to restart the HTTP router on create/delete (fixing 404's) + +# 0.90.66 (09-08-2020) +- Added Import and Export views in Dashboard +- Modified services list sparkline to use start/end of day timestamp +- Modified i18n language files, added go generate script to automatically translate + +# 0.90.65 (09-01-2020) +- Fixed issue with dashboard not logging in (notifier panic) +- Modified static email templates to github.com/statping/emails +- Modified Regenerate API function to keep API_SECRET env +- Added DEMO_MODE env variable, if true, 'admin' cannot be deleted +- Modified Service sparklines on Dashboard +- Added modal popup for UI deletes/edits + +# 0.90.64 (08-18-2020) +- Modified max-width for container to 1012px, larger UI +- Added failure sparklines in the Services list view +- Added "Update Available" alert on the top of Settings if new version is available +- Added Version and Github Commit hash to left navigation on Settings page +- Added "reason" for failures (will be used for more custom notification messages) [regex, lookup, timeout, connection, close, status_code] +- Added Help page that is generated from Statping's Wiki repo on build +- Modified Service Group failures on index page to show 90 days of failures +- Modified Service view page, updated Latency and Ping charts, added failures below +- Modified Service chart on index page to show ping data along with latency +- Added AWS SNS Notifier +- Modified dashboard services UI +- Modified service.Failures API to include 32 failures (max) + +# 0.90.63 (08-17-2020) +- Modified build process to use xgo for all arch builds +- Modified Statping's Push Notifications server notifier to match with Firebase/gorush params + +# 0.90.62 (08-07-2020) +- Added Notification logs +- Fixed issues with Notifer After (x) failures for notifications +- Modified notifications to not send on initial startup +- Updated Incident UI +- Added additional testing for notifications +- Modified SCSS/SASS files to be generated from 1, main.scss to main.css +- Modified index page to use /assets directory for assets, (main.css, style.css) +- Modified index page to use CDN asset paths +- Fixed New Checkin form +- Modified email notifier template to be rendered from MJML (using go generate) +- Modified database relationships with services using gorm +- Modified "statping env" command to show user/group ID +- Removed "js" folder when exporting assets, js files are always version of release, not static JS files + +# 0.90.61 (07-22-2020) +- Modified sass layouts, organized and split up sections +- Modified Checkins to seconds rather than milliseconds (for cronjob) +- Modified Service View page to show data inside cards +- Fixed issue with uptime_data sending incorrect start/end timestamps +- Modified http cache to bypass if url has a "v" query param +- Added "Static Services" (a fake service that requires you to update the online/offline status) +- Added Update Static Service PATCH route (/api/services/{id}) +- Modified SASS api endpoints (base, layout, forms, mixins, mobile, variables) +- Added additional testing +- Modified node version from 10.x to 12.18.2 +- Modified Notifier's struct values to be NullString and NullInt to allow empty values +- Added Search ability to Logs in UI +- Fixed issue with Incidents and Checkins not being deleted once service is deleted + +# 0.90.60 (07-15-2020) +- Added LETSENCRYPT_ENABLE (boolean) env to enable/disable letsencrypt SSL + +# 0.90.59 (07-14-2020) +- Added LetsEncrypt SSL Generator by using LETSENCRYPT_HOST and LETSENCRYPT_EMAIL envs. +- Modified JWT token key to be sha256 of API Secret +- Modified github actions to build multi-arch Docker images +- Added "update" command to install latest version +- Fixed dashboard uptime_data API request to request correct start/time timestamp + +# 0.90.58 (07-09-2020) +- Fixed ICMP latency/ping durations +- Fixed webhook notifier +- Modified file structure for Vue admin dashboard components. +- Added Gotify notifier + +# 0.90.57 (07-04-2020) +- Fixed login issue + # 0.90.56 (06-25-2020) - Modified metrics now include service name for each service metric - Added switch for true/false notifier values diff --git a/CloudronManifest.json b/CloudronManifest.json new file mode 100644 index 00000000..7ae27752 --- /dev/null +++ b/CloudronManifest.json @@ -0,0 +1,19 @@ +{ + "id": "com.statping", + "title": "Statping", + "author": "Hunter Long ", + "description": "Monitor your web services and remote servers", + "tagline": "Server Monitoring", + "version": "0.90.61", + "healthCheckPath": "/health", + "httpPort": 8080, + "addons": { + "localstorage": {} + }, + "manifestVersion": 2, + "website": "https://github.com/statping/statping", + "contactEmail": "info@statping.com", + "icon": "https://assets.statping.com/icon.png", + "tags": [ "monitoring", "uptime" ], + "mediaLinks": [ "https://assets.statping.com/cloudron.png" ] +}s diff --git a/Dockerfile b/Dockerfile index a9846339..44866305 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,18 @@ FROM statping/statping:base AS base - +ARG BUILDPLATFORM # Statping main Docker image that contains all required libraries FROM alpine:latest RUN apk --no-cache add libgcc libstdc++ ca-certificates curl jq && update-ca-certificates COPY --from=base /go/bin/statping /usr/local/bin/ -COPY --from=base /usr/local/bin/sass /usr/local/bin/ +COPY --from=base /root/sassc/bin/sassc /usr/local/bin/ COPY --from=base /usr/local/share/ca-certificates /usr/local/share/ WORKDIR /app VOLUME /app ENV IS_DOCKER=true -ENV SASS=/usr/local/bin/sass +ENV SASS=/usr/local/bin/sassc ENV STATPING_DIR=/app ENV PORT=8080 diff --git a/Dockerfile.base b/Dockerfile.base index 90c349b3..44c8ccb5 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -1,5 +1,6 @@ -FROM node:10.17.0 AS frontend -RUN npm install yarn -g +FROM node:12.18.2-alpine AS frontend +LABEL maintainer="Hunter Long (https://github.com/hunterlong)" +ARG BUILDPLATFORM WORKDIR /statping COPY ./frontend/package.json . COPY ./frontend/yarn.lock . @@ -7,20 +8,28 @@ RUN yarn install --pure-lockfile --network-timeout 1000000 COPY ./frontend . RUN yarn build && yarn cache clean - # Statping Golang BACKEND building from source # Creates "/go/bin/statping" and "/usr/local/bin/sass" for copying FROM golang:1.14-alpine AS backend LABEL maintainer="Hunter Long (https://github.com/hunterlong)" ARG VERSION -RUN apk add --update --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq && \ +ARG COMMIT +ARG BUILDPLATFORM +ARG TARGETARCH +RUN apk add --update --no-cache libstdc++ gcc g++ make git autoconf \ + libtool ca-certificates linux-headers wget curl jq && \ update-ca-certificates -RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \ - chmod +x /usr/local/bin/sass + +WORKDIR /root +RUN git clone https://github.com/sass/sassc.git +RUN . sassc/script/bootstrap && make -C sassc -j4 +# sassc binary: /root/sassc/bin/sassc + WORKDIR /go/src/github.com/statping/statping ADD go.mod go.sum ./ RUN go mod download ENV GO111MODULE on +ENV CGO_ENABLED 1 RUN go get github.com/stretchr/testify/assert && \ go get github.com/stretchr/testify/require && \ go get github.com/GeertJohan/go.rice/rice && \ @@ -28,8 +37,9 @@ RUN go get github.com/stretchr/testify/assert && \ go get github.com/crazy-max/xgo COPY . . COPY --from=frontend /statping/dist/ ./source/dist/ -RUN make clean frontend-copy generate embed build +RUN make clean generate embed +RUN go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo linux" ./cmd RUN chmod a+x statping && mv statping /go/bin/statping # /go/bin/statping - statping binary -# /usr/local/bin/sass - sass binary +# /root/sassc/bin/sassc - sass binary # /statping - Vue frontend (from frontend) diff --git a/Makefile b/Makefile index 98721fcc..571873b9 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,40 @@ VERSION=$(shell cat version.txt) +COMMIT=$(shell git rev-parse HEAD) SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278 BINARY_NAME=statping GOBUILD=go build -a GOVERSION=1.14.0 +NODE_VERSION=12.18.2 XGO=xgo -go $(GOVERSION) --dest=build -BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" +BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" TRVIS_SECRET=O3/2KTOV8krv+yZ1EB/7D1RQRe6NdpFUEJNJkMS/ollYqmz3x2mCO7yIgIJKCKguLXZxjM6CxJcjlCrvUwibL+8BBp7xJe4XFIOrjkPvbbVPry4HkFZCf2GfcUK6o4AByQ+RYqsW2F17Fp9KLQ1rL3OT3eLTwCAGKx3tlY8y+an43zkmo5dN64V6sawx26fh6XTfww590ey+ltgQTjf8UPNup2wZmGvMo9Hwvh/bYR/47bR6PlBh6vhlKWyotKf2Fz1Bevbu0zc35pee5YlsrHR+oSF+/nNd/dOij34BhtqQikUR+zQVy9yty8SlmneVwD3yOENvlF+8roeKIXb6P6eZnSMHvelhWpAFTwDXq2N3d/FIgrQtLxsAFTI3nTHvZgs6OoTd6dA0wkhuIGLxaL3FOeztCdxP5J/CQ9GUcTvifh5ArGGwYxRxQU6rTgtebJcNtXFISP9CEUR6rwRtb6ax7h6f1SbjUGAdxt+r2LbEVEk4ZlwHvdJ2DtzJHT5DQtLrqq/CTUgJ8SJFMkrJMp/pPznKhzN4qvd8oQJXygSXX/gz92MvoX0xgpNeLsUdAn+PL9KketfR+QYosBz04d8k05E+aTqGaU7FUCHPTLwlOFvLD8Gbv0zsC/PWgSLXTBlcqLEz5PHwPVHTcVzspKj/IyYimXpCSbvu1YOIjyc= PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }' 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) +PATH:=$(GOPATH)/bin:$(PATH) OS = freebsd linux openbsd ARCHS = 386 arm amd64 arm64 -all: clean yarn-install compile docker-base docker-vue build-all +all: build-deps compile install test build + +test: clean compile + go test -v -p=1 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -coverprofile=coverage.out ./... + +build: clean + CGO_ENABLED=1 go build -a -ldflags "-s -w -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo osusergo" ./cmd + +go-build: clean + rm -rf source/dist + rm -rf source/rice-box.go + wget https://assets.statping.com/source.tar.gz + tar -xvf source.tar.gz + go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo osusergo" ./cmd + +lint: + go fmt ./... + golint ./... + impi --local github.com/statping/statping/ --scheme stdLocalThirdParty ./... + goimports ./... up: docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans @@ -29,9 +50,6 @@ lite: clean reup: down clean compose-build-full up -test: clean compile - go test -v -p=1 -ldflags="-X main.VERSION=testing" -coverprofile=coverage.out ./... - # build all arch's and release Statping release: test-deps wget -O statping.gpg $(SIGN_URL) @@ -50,7 +68,7 @@ cypress: clean test-api: DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none statping & - sleep 5000 && newman run source/tmpl/postman.json -e dev/postman_environment.json --delay-request 500 + sleep 5000 && newman run dev/postman.json -e dev/postman_environment.json --delay-request 500 test-deps: go get golang.org/x/tools/cmd/cover @@ -58,6 +76,7 @@ test-deps: go get github.com/GeertJohan/go.rice/rice go get github.com/mattn/go-sqlite3 go install github.com/mattn/go-sqlite3 + go install github.com/wellington/go-libsass deps: go get -d -v -t ./... @@ -121,14 +140,17 @@ top: docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml top frontend-build: - rm -rf source/dist && rm -rf frontend/dist + @echo "Removing old frontend distributions..." + @rm -rf source/dist && rm -rf frontend/dist + @echo "yarn install and build static frontend" cd frontend && yarn && yarn build - cp -r frontend/dist source/ && cp -r frontend/src/assets/scss source/dist/ - cp -r source/tmpl/*.* source/dist/ - cp -r frontend/public/favicon source/dist/ - -frontend-copy: - cp -r source/tmpl/*.* source/dist/ + @cp -r frontend/dist source/ + @cp -r frontend/src/assets/scss source/dist/ + @cp frontend/public/favicon.ico source/dist/ + @cp frontend/public/robots.txt source/dist/ + @cp frontend/public/banner.png source/dist/ + @cp -r frontend/public/favicon source/dist/ + @echo "Frontend build complete at ./source/dist" yarn: rm -rf source/dist && rm -rf frontend/dist @@ -138,32 +160,40 @@ yarn: compile: frontend-build rm -f source/rice-box.go cd source && rice embed-go + make generate embed: cd source && rice embed-go -build: - $(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) ./cmd - install: build mv $(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME) install-local: build mv $(BINARY_NAME) /usr/local/bin/$(BINARY_NAME) +install-darwin: + go build -a -ldflags "-X main.VERSION=${VERSION}" -o statping --tags "netgo darwin" ./cmd + mv $(BINARY_NAME) /usr/local/bin/$(BINARY_NAME) + generate: - cd source && go generate + go generate ./... build-all: clean compile build-folders build-linux build-linux-arm build-darwin build-win compress-folders -build-win: - CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc-posix CXX=x86_64-w64-mingw32-g++-posix GO111MODULE="on" GOOS=windows GOARCH=amd64 \ - go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-windows-amd64/statping.exe ./cmd - CGO_ENABLED=1 CC=i686-w64-mingw32-gcc-posix CXX=i686-w64-mingw32-g++-posix GO111MODULE="on" GOOS=windows GOARCH=386 \ - go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-windows-386/statping.exe ./cmd +build-deps: + apt install -y libc6-armel-cross libc6-dev-armel-cross binutils-arm-linux-gnueabi \ + libncurses5-dev build-essential bison flex libssl-dev bc gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf \ + gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libsqlite3-dev gcc-mingw-w64 gcc-mingw-w64-x86-64 build-darwin: - GO111MODULE="on" GOOS=darwin GOARCH=amd64 go build -a -ldflags "-s -w -X main.VERSION=${VERSION}" -o releases/statping-darwin-amd64/statping --tags "netgo darwin" ./cmd + GO111MODULE="on" GOOS=darwin GOARCH=amd64 \ + go build -a -ldflags "-s -w -X main.VERSION=${VERSION}" -o releases/statping-darwin-amd64/statping --tags "netgo darwin" ./cmd + +build-win: + CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GO111MODULE="on" GOOS=windows GOARCH=amd64 \ + go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-windows-amd64/statping.exe ./cmd + CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GO111MODULE="on" GOOS=windows GOARCH=386 \ + go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-windows-386/statping.exe ./cmd build-linux: CGO_ENABLED=1 GO111MODULE="on" GOOS=linux GOARCH=amd64 \ @@ -172,16 +202,18 @@ build-linux: go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-386/statping --tags "netgo linux" ./cmd build-linux-arm: + CGO_ENABLED=1 CC=arm-linux-gnueabihf-gcc-6 CXX=arm-linux-gnueabihf-g++-6 GO111MODULE="on" GOOS=linux GOARCH=arm GOARM=6 \ + go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-arm6/statping --tags "netgo" ./cmd CGO_ENABLED=1 CC=arm-linux-gnueabihf-gcc-6 CXX=arm-linux-gnueabihf-g++-6 GO111MODULE="on" GOOS=linux GOARCH=arm GOARM=7 \ - go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-arm/statping ./cmd + go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-arm7/statping --tags "netgo" ./cmd CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc-6 CXX=aarch64-linux-gnu-g++-6 GO111MODULE="on" GOOS=linux GOARCH=arm64 \ - go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-arm64/statping ./cmd + go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-arm64/statping --tags "netgo" ./cmd build-folders: mkdir build || true for os in windows darwin linux;\ do \ - for arch in 386 amd64 arm arm64;\ + for arch in 386 amd64 arm6 arm7 arm64;\ do \ mkdir -p releases/statping-$$os-$$arch/; \ done \ @@ -191,7 +223,7 @@ compress-folders: mkdir build || true for os in darwin linux;\ do \ - for arch in 386 amd64 arm arm64;\ + for arch in 386 amd64 arm6 arm7 arm64;\ do \ chmod +x releases/statping-$$os-$$arch/statping || true; \ tar -czf releases/statping-$$os-$$arch.tar.gz -C releases/statping-$$os-$$arch statping || true; \ @@ -208,26 +240,26 @@ compress-folders: # remove files for a clean compile/build clean: - rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor} - rm -rf cmd/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,*.html,*.json} - rm -rf core/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - rm -rf types/notifications/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - rm -rf handlers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - rm -rf notifiers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - rm -rf source/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - rm -rf types/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - rm -rf utils/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} - 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 frontend/cypress/videos - rm -f coverage.* sass - 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,source/build,snap} + @echo "Cleaning temporary and build folders..." + @rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor} + @rm -rf cmd/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,*.html,*.json} + @rm -rf core/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @rm -rf types/notifications/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @rm -rf handlers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @rm -rf notifiers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @rm -rf source/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @rm -rf types/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @rm -rf utils/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + @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 frontend/cypress/videos + @rm -f coverage.* sass + @rm -rf **/*.db-journal + @find . -name "*.out" -type f -delete + @find . -name "*.cpu" -type f -delete + @find . -name "*.mem" -type f -delete + @rm -rf {build,releases,tmp,source/build,snap,parts,prime,snap,stage} + @echo "Finished removing temporary and build folders" print_details: @echo \==== Statping Development Instance ==== @@ -279,11 +311,14 @@ post-release: frontend-build upload_to_s3 publish-homebrew dockerhub publish-homebrew: curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/statping%2Fhomebrew-statping/requests -upload_to_s3: travis_s3_creds - aws s3 cp ./source/dist/css $(ASSETS_BKT) --recursive --exclude "*" --include "*.css" - aws s3 cp ./source/dist/js $(ASSETS_BKT) --recursive --exclude "*" --include "*.js" - aws s3 cp ./source/dist/scss $(ASSETS_BKT) --recursive --exclude "*" --include "*.scss" - aws s3 cp ./install.sh $(ASSETS_BKT) +upload_to_s3: + tar -czvf source.tar.gz source/ + aws s3 cp source.tar.gz s3://assets.statping.com/ + rm -rf source.tar.gz + aws s3 cp source/dist/css/ s3://assets.statping.com/css/ --recursive --exclude "*" --include "*.css" + aws s3 cp source/dist/js/ s3://assets.statping.com/js/ --recursive --exclude "*" --include "*.js" + aws s3 cp source/dist/scss/ s3://assets.statping.com/scss/ --recursive --exclude "*" --include "*.scss" + aws s3 cp install.sh s3://assets.statping.com/ travis_s3_creds: mkdir -p ~/.aws @@ -296,11 +331,25 @@ valid-sign: gpg --verify statping.asc sentry-release: - sentry-cli releases new -p backend -p frontend v${VERSION} - sentry-cli releases set-commits --auto v${VERSION} - sentry-cli releases finalize v${VERSION} + sentry-cli releases --org statping --project backend new v${VERSION} + sentry-cli releases --org statping --project backend set-commits v${VERSION} --auto + sentry-cli releases --org statping --project backend finalize v${VERSION} + sentry-cli releases --org statping --project frontend new v${VERSION} + sentry-cli releases --org statping --project frontend set-commits v${VERSION} --auto + sentry-cli releases --org statping --project frontend finalize v${VERSION} -snapcraft: clean compile build-linux +download-bins: clean + mkdir build || true + wget "https://github.com/statping/statping/releases/download/v${VERSION}/statping-linux-386.tar.gz" + wget "https://github.com/statping/statping/releases/download/v${VERSION}/statping-linux-amd64.tar.gz" + wget "https://github.com/statping/statping/releases/download/v${VERSION}/statping-linux-arm.tar.gz" + wget "https://github.com/statping/statping/releases/download/v${VERSION}/statping-linux-arm64.tar.gz" + mv statping-linux-386.tar.gz build/ + mv statping-linux-amd64.tar.gz build/ + mv statping-linux-arm.tar.gz build/ + mv statping-linux-arm64.tar.gz build/ + +snapcraft: download-bins mkdir snap mv snapcraft.yaml snap/ PWD=$(shell pwd) @@ -331,5 +380,54 @@ certs: -keyout key.pem \ -subj "/C=US/ST=California/L=Santa Monica/O=Statping/OU=Development/CN=localhost" -.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 +xgo-latest: + xgo --go $(GOVERSION) --targets=linux/amd64,linux/386,linux/arm-7,linux/arm-6,linux/arm64,windows/386,windows/amd64,darwin/386,darwin/amd64 --out='statping' --pkg='cmd' --dest=build --tags 'netgo' --ldflags='-X main.VERSION=${VERSION} -X main.COMMIT=$(COMMIT) -linkmode external -extldflags "-static"' . + +buildx-latest: multiarch + docker buildx create --name statping-latest + docker buildx inspect --builder statping-latest --bootstrap + docker buildx build --builder statping-latest --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile -t statping/statping:latest -t statping/statping:v${VERSION} --build-arg=VERSION=${VERSION} --build-arg=COMMIT=${COMMIT} . + docker buildx rm statping-latest + +buildx-dev: multiarch + docker buildx create --name statping-dev + docker buildx inspect --builder statping-dev --bootstrap + docker buildx build --builder statping-dev --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile -t statping/statping:dev --build-arg=VERSION=${VERSION} --build-arg=COMMIT=${COMMIT} . + docker buildx rm statping-dev + +buildx-base: multiarch + docker buildx create --name statping-base + docker buildx inspect --builder statping-base --bootstrap + docker buildx build --builder statping-base --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile.base -t statping/statping:base --build-arg=VERSION=${VERSION} --build-arg=COMMIT=${COMMIT} . + docker buildx rm statping-base + +multiarch: + mkdir /tmp/.buildx-cache || true + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + +delve: + go build -gcflags "all=-N -l" -o statping ./cmd + dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./statping + +check: + @echo "Checking the programs required for the build are installed..." + @echo "go: $(shell go version) - $(shell which go)" && go version >/dev/null 2>&1 || (echo "ERROR: go 1.14 is required."; exit 1) + @echo "node: $(shell node --version) - $(shell which node)" && node --version >/dev/null 2>&1 || (echo "ERROR: node 12.x is required."; exit 1) + @echo "yarn: $(shell yarn --version) - $(shell which yarn)" && yarn --version >/dev/null 2>&1 || (echo "ERROR: yarn is required."; exit 1) + @echo "All required programs are installed!" + +#sentry-release: +# sentry-cli releases new -p $SENTRY_PROJECT $VERSION +# sentry-cli releases set-commits --auto $VERSION +# sentry-cli releases files $VERSION upload-sourcemaps dist + +gen_help: + for file in ./statping.wiki/*.md + do + # convert each file to html and place it in the html directory + # --gfm == use github flavoured markdown + marked -o html/$file.html $file --gfm + done + +.PHONY: all check build certs multiarch install-darwin go-build build-all buildx-base buildx-dev buildx-latest 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/app.json b/app.json index 6d73c60d..a4f0abfb 100644 --- a/app.json +++ b/app.json @@ -2,6 +2,6 @@ "name": "Statping", "description": "Statping Server Monitoring with Status Page", "repository": "https://github.com/statping/statping", - "logo": "https://raw.githubusercontent.com/statping/statping/master/source/tmpl/banner.png", + "logo": "https://assets.statping.com/banner.png", "keywords": ["statping", "server", "monitoring", "status page","golang", "go"] } diff --git a/cmd/cli.go b/cmd/cli.go index 672fb676..4f06b8e1 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -5,21 +5,23 @@ import ( "encoding/json" "fmt" "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" + "path/filepath" "strings" "time" ) +var ( + importAll *bool +) + func assetsCli() error { dir := utils.Directory if err := utils.InitLogs(); err != nil { @@ -34,12 +36,81 @@ func assetsCli() error { return nil } +func systemctlCli(dir string, uninstall bool, port int64) error { + location := "/etc/systemd/system/statping.service" + + if uninstall { + fmt.Println("systemctl stop statping") + if _, _, err := utils.Command("systemctl", "stop", "statping"); err != nil { + log.Errorln(err) + } + fmt.Println("systemctl disable statping") + if _, _, err := utils.Command("systemctl", "disable", "statping"); err != nil { + log.Errorln(err) + } + fmt.Println("Deleting systemctl: ", location) + if err := utils.DeleteFile(location); err != nil { + log.Errorln(err) + } + return nil + } + if ok := utils.FolderExists(dir); !ok { + return errors.New("directory does not exist: " + dir) + } + + binPath, err := os.Executable() + if err != nil { + return err + } + + config := []byte(`[Unit] +Description=Statping Server +After=network.target +After=systemd-user-sessions.service +After=network-online.target + +[Service] +Type=simple +Restart=always +Environment="STATPING_DIR=` + dir + `" +Environment="ALLOW_REPORTS=true" +ExecStart=` + binPath + ` --port=` + utils.ToString(port) + ` +WorkingDirectory=` + dir + ` + +[Install] +WantedBy=multi-user.target" +`) + fmt.Println("Saving systemctl service to: ", location) + fmt.Printf("Using directory %s for Statping data\n", dir) + fmt.Printf("Running on port %d\n", port) + fmt.Printf("\n\n%s\n\n", string(config)) + if err := utils.SaveFile(location, config); err != nil { + return err + } + fmt.Println("systemctl daemon-reload") + if _, _, err := utils.Command("systemctl", "daemon-reload"); err != nil { + return err + } + fmt.Println("systemctl enable statping") + if _, _, err := utils.Command("systemctl", "enable", "statping.service"); err != nil { + return err + } + fmt.Println("systemctl start statping") + if _, _, err := utils.Command("systemctl", "start", "statping"); err != nil { + return err + } + fmt.Println("Statping was will auto start on reboots") + fmt.Println("systemctl service: ", location) + + return nil +} + func exportCli(args []string) error { - filename := fmt.Sprintf("%s/statping-%s.json", utils.Directory, time.Now().Format("01-02-2006-1504")) + filename := filepath.Join(utils.Directory, time.Now().Format("01-02-2006-1504")+".json") if len(args) == 1 { filename = fmt.Sprintf("%s/%s", utils.Directory, args) } - var data []byte + var data *handlers.ExportData if err := utils.InitLogs(); err != nil { return err } @@ -56,10 +127,10 @@ func exportCli(args []string) error { if _, err := services.SelectAllServices(false); err != nil { return err } - if data, err = ExportSettings(); err != nil { + if data, err = handlers.ExportSettings(); err != nil { return fmt.Errorf("could not export settings: %v", err.Error()) } - if err = utils.SaveFile(filename, data); err != nil { + if err = utils.SaveFile(filename, data.JSON()); err != nil { return fmt.Errorf("could not write file statping-export.json: %v", err.Error()) } log.Infoln("Statping export file saved to ", filename) @@ -73,7 +144,7 @@ func sassCli() error { if err := source.Assets(); err != nil { return err } - if err := source.CompileSASS(source.DefaultScss...); err != nil { + if err := source.CompileSASS(); err != nil { return err } return nil @@ -128,6 +199,10 @@ func resetCli() error { func envCli() error { fmt.Println("Statping Configuration") + fmt.Printf("Process ID: %d\n", os.Getpid()) + fmt.Printf("Running as user id: %d\n", os.Getuid()) + fmt.Printf("Running as group id: %d\n", os.Getgid()) + fmt.Printf("Statping Directory: %s\n", utils.Directory) for k, v := range utils.Params.AllSettings() { fmt.Printf("%s=%v\n", strings.ToUpper(k), v) } @@ -153,20 +228,54 @@ func onceCli() error { func importCli(args []string) error { var err error var data []byte - filename := args[1] - if data, err = ioutil.ReadFile(filename); err != nil { + if len(args) < 1 { + return errors.New("invalid command arguments") + } + if data, err = ioutil.ReadFile(args[0]); err != nil { return err } - var exportData ExportData + 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)) + if exportData.Config != nil { + log.Printf("Configs: %s\n", exportData.Config.DbConn) + if exportData.Config.DbUser != "" { + log.Printf(" - Host: %s\n", exportData.Config.DbHost) + log.Printf(" - User: %s\n", exportData.Config.DbUser) + } + } + if len(exportData.Services) > 0 { + log.Printf("Services: %d\n", len(exportData.Services)) + } + if len(exportData.Checkins) > 0 { + log.Printf("Checkins: %d\n", len(exportData.Checkins)) + } + if len(exportData.Groups) > 0 { + log.Printf("Groups: %d\n", len(exportData.Groups)) + } + if len(exportData.Messages) > 0 { + log.Printf("Messages: %d\n", len(exportData.Messages)) + } + if len(exportData.Incidents) > 0 { + log.Printf("Incidents: %d\n", len(exportData.Incidents)) + } + if len(exportData.Users) > 0 { + log.Printf("Users: %d\n", len(exportData.Users)) + } + + if exportData.Config != nil { + if ask("Create config.yml file from Configs?") { + log.Printf("Database Host: %s\n", exportData.Config.DbHost) + log.Printf("Database Port: %d\n", exportData.Config.DbPort) + log.Printf("Database User: %s\n", exportData.Config.DbUser) + log.Printf("Database Password: %s\n", exportData.Config.DbPass) + if err := exportData.Config.Save(utils.Directory); err != nil { + return err + } + } + } config, err := configs.LoadConfigs(configFile) if err != nil { @@ -175,21 +284,22 @@ func importCli(args []string) error { 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("Create database rows and sample data?") { + if err := config.ResetCore(); err != nil { + return err + } } - if ask("Import Core settings?") { c := exportData.Core if err := c.Update(); err != nil { - return err + log.Errorln(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 + log.Errorln(err) } } } @@ -197,7 +307,7 @@ func importCli(args []string) error { if ask(fmt.Sprintf("Import Service '%s'?", s.Name)) { s.Id = 0 if err := s.Create(); err != nil { - return err + log.Errorln(err) } } } @@ -205,7 +315,7 @@ func importCli(args []string) error { if ask(fmt.Sprintf("Import Checkin '%s'?", s.Name)) { s.Id = 0 if err := s.Create(); err != nil { - return err + log.Errorln(err) } } } @@ -213,7 +323,7 @@ func importCli(args []string) error { if ask(fmt.Sprintf("Import Message '%s'?", s.Title)) { s.Id = 0 if err := s.Create(); err != nil { - return err + log.Errorln(err) } } } @@ -221,7 +331,7 @@ func importCli(args []string) error { if ask(fmt.Sprintf("Import User '%s'?", s.Username)) { s.Id = 0 if err := s.Create(); err != nil { - return err + log.Errorln(err) } } } @@ -230,6 +340,7 @@ func importCli(args []string) error { } func ask(format string) bool { + fmt.Printf(fmt.Sprintf(format + " [y/N]: ")) reader := bufio.NewReader(os.Stdin) text, _ := reader.ReadString('\n') @@ -375,15 +486,16 @@ type gitUploader struct { // 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"` -} +//type ExportData struct { +// Config *configs.DbConfig `json:"config"` +// 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 @@ -393,28 +505,35 @@ type ExportData struct { // - Services // - Groups // - Messages -func ExportSettings() ([]byte, error) { - c, err := core.Select() - if err != nil { - return nil, err - } - var srvs []services.Service - for _, s := range services.AllInOrder() { - s.Failures = nil - srvs = append(srvs, s) - } - data := ExportData{ - Core: c, - Notifiers: core.App.Notifications, - Checkins: checkins.All(), - Users: users.All(), - Services: srvs, - Groups: groups.All(), - Messages: messages.All(), - } - export, err := json.Marshal(data) - return export, err -} +//func ExportSettings() ([]byte, error) { +// c, err := core.Select() +// if err != nil { +// return nil, err +// } +// var srvs []services.Service +// for _, s := range services.AllInOrder() { +// s.Failures = nil +// srvs = append(srvs, s) +// } +// +// cfg, err := configs.LoadConfigs(configFile) +// if err != nil { +// return nil, err +// } +// +// data := ExportData{ +// Config: cfg, +// Core: c, +// Notifiers: core.App.Notifications, +// Checkins: checkins.All(), +// Users: users.All(), +// Services: srvs, +// 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 { diff --git a/cmd/cli_test.go b/cmd/cli_test.go index 94de537d..586db178 100644 --- a/cmd/cli_test.go +++ b/cmd/cli_test.go @@ -2,10 +2,12 @@ package main import ( "bytes" + "github.com/statping/statping/source" "github.com/statping/statping/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "io/ioutil" + "os" "testing" ) @@ -18,16 +20,15 @@ func init() { } func TestStatpingDirectory(t *testing.T) { - dir := utils.Directory - require.NotContains(t, dir, "/cmd") - require.NotEmpty(t, dir) - dir = utils.Params.GetString("STATPING_DIR") require.NotContains(t, dir, "/cmd") require.NotEmpty(t, dir) } func TestEnvCLI(t *testing.T) { + os.Setenv("API_SECRET", "demoapisecret123") + os.Setenv("SASS", "/usr/local/bin/sass") + cmd := rootCmd b := bytes.NewBufferString("") cmd.SetOut(b) @@ -39,6 +40,12 @@ func TestEnvCLI(t *testing.T) { assert.Contains(t, string(out), VERSION) assert.Contains(t, utils.Directory, string(out)) assert.Contains(t, "SAMPLE_DATA=true", string(out)) + assert.Contains(t, "API_SECRET=demoapisecret123", string(out)) + assert.Contains(t, "STATPING_DIR="+dir, string(out)) + assert.Contains(t, "SASS=/usr/local/bin/sass", string(out)) + + os.Unsetenv("API_SECRET") + os.Unsetenv("SASS") } func TestVersionCLI(t *testing.T) { @@ -58,16 +65,26 @@ func TestAssetsCLI(t *testing.T) { b := bytes.NewBufferString("") cmd.SetOut(b) cmd.SetArgs([]string{"assets"}) - cmd.Execute() + 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") + for _, f := range source.RequiredFiles { + assert.FileExists(t, utils.Directory+"/assets/"+f) + } +} + +func TestUpdateCLI(t *testing.T) { + cmd := rootCmd + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.SetArgs([]string{"update"}) + err := cmd.Execute() + require.Nil(t, err) + out, err := ioutil.ReadAll(b) + require.Nil(t, err) + assert.Contains(t, string(out), VERSION) } func TestHelpCLI(t *testing.T) { diff --git a/cmd/commands.go b/cmd/commands.go index df7629f7..b2850854 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -1,23 +1,70 @@ package main import ( + "bytes" "fmt" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/statping/statping/utils" + "io" "os" + "os/exec" ) var rootCmd = &cobra.Command{ - Use: "statping", - Short: "A simple Application Status Monitor that is opensource and lightweight.", + Use: "statping", + Version: VERSION, + Short: "A simple Application Status Monitor that is opensource and lightweight.", Run: func(cmd *cobra.Command, args []string) { start() }, } +var updateCmd = &cobra.Command{ + Use: "update", + Example: "statping update", + Short: "Update to the latest version", + RunE: func(cmd *cobra.Command, args []string) error { + log.Infoln("Updating Statping to the latest version...") + log.Infoln("curl -o- -L https://statping.com/install.sh | bash") + curl, err := exec.LookPath("curl") + if err != nil { + return err + } + bash, err := exec.LookPath("bash") + if err != nil { + return err + } + + ree := bytes.NewBuffer(nil) + + c1 := exec.Command(curl, "-o-", "-L", "https://statping.com/install.sh") + c2 := exec.Command(bash) + + r, w := io.Pipe() + c1.Stdout = w + c2.Stdin = r + + var b2 bytes.Buffer + c2.Stdout = &b2 + + c1.Start() + c2.Start() + c1.Wait() + w.Close() + c2.Wait() + io.Copy(ree, &b2) + + log.Infoln(ree.String()) + os.Exit(0) + return nil + }, +} + var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of Statping", + Use: "version", + Example: "statping version", + Short: "Print the version number of Statping", Run: func(cmd *cobra.Command, args []string) { if COMMIT != "" { fmt.Printf("%s (%s)\n", VERSION, COMMIT) @@ -28,9 +75,38 @@ var versionCmd = &cobra.Command{ }, } +var systemctlCmd = &cobra.Command{ + Use: "systemctl [install/uninstall]", + Example: "statping systemctl install", + Short: "Install or Uninstall systemctl services", + RunE: func(cmd *cobra.Command, args []string) error { + if args[1] == "install" { + if len(args) < 3 { + return errors.New("requires 'install '") + } + } + port := utils.ToInt(args[2]) + if port == 0 { + port = 80 + } + if err := systemctlCli(args[1], args[0] == "uninstall", port); err != nil { + return err + } + os.Exit(0) + return nil + }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("requires 'install ' or 'uninstall' as arguments") + } + return nil + }, +} + var assetsCmd = &cobra.Command{ - Use: "assets", - Short: "Dump all assets used locally to be edited", + Use: "assets", + Example: "statping assets", + Short: "Dump all assets used locally to be edited", RunE: func(cmd *cobra.Command, args []string) error { if err := assetsCli(); err != nil { return err @@ -41,8 +117,9 @@ var assetsCmd = &cobra.Command{ } var exportCmd = &cobra.Command{ - Use: "export", - Short: "Exports your Statping settings to a 'statping-export.json' file.", + Use: "export", + Example: "statping export", + Short: "Exports your Statping settings to a 'statping-export.json' file.", RunE: func(cmd *cobra.Command, args []string) error { if err := exportCli(args); err != nil { return err @@ -53,8 +130,9 @@ var exportCmd = &cobra.Command{ } var sassCmd = &cobra.Command{ - Use: "sass", - Short: "Compile .scss files into the css directory", + Use: "sass", + Example: "statping sass", + Short: "Compile .scss files into the css directory", RunE: func(cmd *cobra.Command, args []string) error { if err := sassCli(); err != nil { return err @@ -65,8 +143,9 @@ var sassCmd = &cobra.Command{ } var envCmd = &cobra.Command{ - Use: "env", - Short: "Return the configs that will be ran", + Use: "env", + Example: "statping env", + Short: "Return the configs that will be ran", RunE: func(cmd *cobra.Command, args []string) error { if err := envCli(); err != nil { return err @@ -77,8 +156,9 @@ var envCmd = &cobra.Command{ } var resetCmd = &cobra.Command{ - Use: "reset", - Short: "Start a fresh copy of Statping", + Use: "reset", + Example: "statping reset", + Short: "Start a fresh copy of Statping", RunE: func(cmd *cobra.Command, args []string) error { if err := resetCli(); err != nil { return err @@ -89,8 +169,9 @@ var resetCmd = &cobra.Command{ } var onceCmd = &cobra.Command{ - Use: "once", - Short: "Check all services 1 time and then quit", + Use: "once", + Example: "statping once", + Short: "Check all services 1 time and then quit", RunE: func(cmd *cobra.Command, args []string) error { if err := onceCli(); err != nil { return err @@ -101,8 +182,9 @@ var onceCmd = &cobra.Command{ } var importCmd = &cobra.Command{ - Use: "import [.json file]", - Short: "Imports settings from a previously saved JSON file.", + Use: "import [.json file]", + Example: "statping import backup.json", + Short: "Imports settings from a previously saved JSON file.", RunE: func(cmd *cobra.Command, args []string) error { if err := importCli(args); err != nil { return err diff --git a/cmd/main.go b/cmd/main.go index 87c2d72e..d3f7e082 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,16 +29,20 @@ var ( func init() { stopped = make(chan bool, 1) - core.New(VERSION) + core.New(VERSION, COMMIT) utils.InitEnvs() + utils.Params.Set("VERSION", VERSION) + utils.Params.Set("COMMIT", COMMIT) rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(assetsCmd) rootCmd.AddCommand(exportCmd) rootCmd.AddCommand(importCmd) rootCmd.AddCommand(sassCmd) rootCmd.AddCommand(onceCmd) rootCmd.AddCommand(envCmd) + rootCmd.AddCommand(systemctlCmd) rootCmd.AddCommand(resetCmd) parseFlags(rootCmd) @@ -96,39 +100,8 @@ func start() { exit(err) } - if !confgs.Db.HasTable("core") { - var srvs int64 - if confgs.Db.HasTable(&services.Service{}) { - confgs.Db.Model(&services.Service{}).Count(&srvs) - if srvs > 0 { - exit(errors.Wrap(err, "there are already services setup.")) - return - } - } - - if err := confgs.DropDatabase(); err != nil { - exit(errors.Wrap(err, "error dropping database")) - } - - if err := confgs.CreateDatabase(); err != nil { - exit(errors.Wrap(err, "error creating database")) - } - - if err := configs.CreateAdminUser(confgs); err != nil { - exit(errors.Wrap(err, "error creating default admin user")) - } - - if utils.Params.GetBool("SAMPLE_DATA") { - log.Infoln("Adding Sample Data") - if err := configs.TriggerSamples(); err != nil { - exit(errors.Wrap(err, "error adding sample data")) - } - } else { - if err := core.Samples(); err != nil { - exit(errors.Wrap(err, "error added core details")) - } - } - + if err = confgs.ResetCore(); err != nil { + exit(err) } if err = confgs.DatabaseChanges(); err != nil { @@ -175,20 +148,20 @@ func InitApp() error { if _, err := core.Select(); err != nil { return err } + // init Sentry error monitoring (its useful) + utils.SentryInit(core.App.AllowReports.Bool) // init prometheus metrics metrics.InitMetrics() + // connect each notifier, added them into database if needed + notifiers.InitNotifiers() // select all services in database and store services in a mapping of Service pointers if _, err := services.SelectAllServices(true); err != nil { return err } // start routines for each service checking process services.CheckServices() - // connect each notifier, added them into database if needed - notifiers.InitNotifiers() // start routine to delete old records (failures, hits) go database.Maintenance() - // init Sentry error monitoring (its useful) - utils.SentryInit(&VERSION, core.App.AllowReports.Bool) core.App.Setup = true core.App.Started = utils.Now() return nil diff --git a/database/database.go b/database/database.go index c6136cfc..79b6fc2a 100644 --- a/database/database.go +++ b/database/database.go @@ -169,10 +169,6 @@ func Available(db Database) bool { return true } -func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { - return db.Where("service = ?", 1000) -} - func (it *Db) MultipleSelects(args ...string) Database { joined := strings.Join(args, ", ") return it.Select(joined) @@ -181,6 +177,7 @@ func (it *Db) MultipleSelects(args ...string) Database { type Db struct { Database *gorm.DB Type string + ReadOnly bool } // Openw is a drop-in replacement for Open() @@ -223,6 +220,9 @@ func OpenTester() (Database, error) { default: dbString = fmt.Sprintf("file:%s?mode=memory&cache=shared", utils.RandomString(12)) } + if utils.Params.IsSet("DB_DSN") { + dbString = utils.Params.GetString("DB_DSN") + } newDb, err := Openw(testDB, dbString) if err != nil { return nil, err @@ -239,6 +239,7 @@ func Wrap(db *gorm.DB) Database { return &Db{ Database: db, Type: db.Dialect().GetName(), + ReadOnly: utils.Params.GetBool("READ_ONLY"), } } @@ -379,14 +380,26 @@ func (it *Db) Related(value interface{}, foreignKeys ...string) Database { } func (it *Db) FirstOrInit(out interface{}, where ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.FirstOrInit(out, where...)) } func (it *Db) FirstOrCreate(out interface{}, where ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.FirstOrCreate(out, where...)) } func (it *Db) Update(attrs ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Update(attrs...)) } @@ -395,22 +408,42 @@ func (it *Db) Updates(values interface{}, ignoreProtectedAttrs ...bool) Database } func (it *Db) UpdateColumn(attrs ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.UpdateColumn(attrs...)) } func (it *Db) UpdateColumns(values interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.UpdateColumns(values)) } func (it *Db) Save(value interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Save(value)) } func (it *Db) Create(value interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Create(value)) } func (it *Db) Delete(value interface{}, where ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Delete(value, where...)) } @@ -435,14 +468,26 @@ func (it *Db) Debug() Database { } func (it *Db) Begin() Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Begin()) } func (it *Db) Commit() Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Commit()) } func (it *Db) Rollback() Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.Rollback()) } @@ -455,14 +500,26 @@ func (it *Db) RecordNotFound() bool { } func (it *Db) CreateTable(values ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.CreateTable(values...)) } func (it *Db) DropTable(values ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.DropTable(values...)) } func (it *Db) DropTableIfExists(values ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.DropTableIfExists(values...)) } @@ -471,26 +528,50 @@ func (it *Db) HasTable(value interface{}) bool { } func (it *Db) AutoMigrate(values ...interface{}) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.AutoMigrate(values...)) } func (it *Db) ModifyColumn(column string, typ string) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.ModifyColumn(column, typ)) } func (it *Db) DropColumn(column string) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.DropColumn(column)) } func (it *Db) AddIndex(indexName string, columns ...string) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.AddIndex(indexName, columns...)) } func (it *Db) AddUniqueIndex(indexName string, columns ...string) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.AddUniqueIndex(indexName, columns...)) } func (it *Db) RemoveIndex(indexName string) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.RemoveIndex(indexName)) } @@ -519,6 +600,10 @@ func (it *Db) SetJoinTableHandler(source interface{}, column string, handler gor } func (it *Db) AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database { + if it.ReadOnly { + it.Database.Error = nil + return Wrap(it.Database) + } return Wrap(it.Database.AddForeignKey(field, dest, onDelete, onUpdate)) } diff --git a/database/grouping.go b/database/grouping.go index 54544870..28bce658 100644 --- a/database/grouping.go +++ b/database/grouping.go @@ -39,7 +39,7 @@ type GroupQuery struct { } func (b GroupQuery) Find(data interface{}) error { - return b.db.Find(data).Error() + return b.db.Order("id DESC").Find(data).Error() } func (b GroupQuery) Database() Database { @@ -70,27 +70,28 @@ func (t *TimeVar) ToValues() ([]*TimeValue, error) { } // GraphData will return all hits or failures -func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) { - dbQuery := g.db.MultipleSelects( - g.db.SelectByTime(g.Group), +func (b *GroupQuery) GraphData(by By) ([]*TimeValue, error) { + b.db = b.db.MultipleSelects( + b.db.SelectByTime(b.Group), by.String(), ).Group("timeframe").Order("timeframe", true) - g.db = dbQuery - - caller, err := g.ToTimeValue() + caller, err := b.ToTimeValue() if err != nil { return nil, err } - if g.FillEmpty { - return caller.FillMissing(g.Start, g.End) + if b.FillEmpty { + return caller.FillMissing(b.Start, b.End) } return caller.ToValues() } -func (g *GroupQuery) ToTimeValue() (*TimeVar, error) { - rows, err := g.db.Rows() +// ToTimeValue will format the SQL rows into a JSON format for the API. +// [{"timestamp": "2006-01-02T15:04:05Z", "amount": 468293}] +// TODO redo this entire function, use better SQL query to group by time +func (b *GroupQuery) ToTimeValue() (*TimeVar, error) { + rows, err := b.db.Rows() if err != nil { return nil, err } @@ -101,8 +102,8 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) { if err := rows.Scan(&timeframe, &amount); err != nil { log.Error(err, timeframe) } - trueTime, _ := g.db.ParseTime(timeframe) - newTs := types.FixedTime(trueTime, g.Group) + trueTime, _ := b.db.ParseTime(timeframe) + newTs := types.FixedTime(trueTime, b.Group) tv := &TimeValue{ Timeframe: newTs, @@ -110,33 +111,32 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) { } data = append(data, tv) } - return &TimeVar{g, data}, nil + return &TimeVar{b, data}, nil } func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) { timeMap := make(map[string]int64) var validSet []*TimeValue - dur := t.g.Group for _, v := range t.data { timeMap[v.Timeframe] = v.Amount } - currentStr := types.FixedTime(current, t.g.Group) - for { + currentStr := types.FixedTime(current, t.g.Group) + var amount int64 if timeMap[currentStr] != 0 { amount = timeMap[currentStr] } + validSet = append(validSet, &TimeValue{ Timeframe: currentStr, Amount: amount, }) + current = current.Add(t.g.Group) if current.After(end) { break } - current = current.Add(dur) - currentStr = types.FixedTime(current, t.g.Group) } return validSet, nil @@ -233,10 +233,6 @@ func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) { if endField == 0 { query.End = utils.Now() } - if query.End.After(utils.Now()) { - query.End = utils.Now() - } - if query.Limit != 0 { q = q.Limit(query.Limit) } diff --git a/database/routines.go b/database/routines.go index 92583e64..591277da 100644 --- a/database/routines.go +++ b/database/routines.go @@ -16,6 +16,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 +// env: REMOVE_AFTER - golang duration parsed time for deleting records older than REMOVE_AFTER duration from now +// env: CLEANUP_INTERVAL - golang duration parsed time for checking old records routine func Maintenance() { dur := utils.Params.GetDuration("REMOVE_AFTER") interval := utils.Params.GetDuration("CLEANUP_INTERVAL") diff --git a/database/time.go b/database/time.go index ea41f77d..db690bab 100644 --- a/database/time.go +++ b/database/time.go @@ -19,10 +19,9 @@ func (it *Db) ParseTime(t string) (time.Time, error) { } } +// FormatTime returns the timestamp in the same format as the DATETIME column in database func (it *Db) FormatTime(t time.Time) string { switch it.Type { - case "mysql": - return t.Format("2006-01-02 15:04:05") case "postgres": return t.Format("2006-01-02 15:04:05.999999999") default: @@ -30,6 +29,7 @@ func (it *Db) FormatTime(t time.Time) string { } } +// SelectByTime returns an SQL query that will group "created_at" column by x seconds and returns as "timeframe" func (it *Db) SelectByTime(increment time.Duration) string { seconds := int64(increment.Seconds()) switch it.Type { @@ -41,33 +41,3 @@ func (it *Db) SelectByTime(increment time.Duration) string { return fmt.Sprintf("datetime((strftime('%%s', created_at) / %d) * %d, 'unixepoch') as timeframe", seconds, seconds) } } - -func (it *Db) correctTimestamp(increment string) string { - var timestamper string - switch increment { - case "second": - timestamper = "%Y-%m-%d %H:%M:%S" - case "minute": - timestamper = "%Y-%m-%d %H:%M:00" - case "hour": - timestamper = "%Y-%m-%d %H:00:00" - case "day": - timestamper = "%Y-%m-%d 00:00:00" - case "month": - timestamper = "%Y-%m-01 00:00:00" - case "year": - timestamper = "%Y-01-01 00:00:00" - default: - timestamper = "%Y-%m-%d 00:00:00" - } - - switch it.Type { - case "mysql": - case "second": - timestamper = "%Y-%m-%d %H:%i:%S" - case "minute": - timestamper = "%Y-%m-%d %H:%i:00" - } - - return timestamper -} diff --git a/dev/kubernetes.yml b/dev/kubernetes.yml new file mode 100644 index 00000000..15033baa --- /dev/null +++ b/dev/kubernetes.yml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Service +metadata: + name: statping +spec: + ports: + - port: 8080 + selector: + app: statping + clusterIP: None +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: statping +spec: + selector: + matchLabels: + app: statping + strategy: + type: Recreate + template: + metadata: + labels: + app: statping + spec: + containers: + - image: statping/statping + name: statping + env: + - name: ALLOW_REPORTS + value: "true" + ports: + - containerPort: 8080 + name: statping + volumeMounts: + - name: statping-storage + mountPath: /app + volumes: + - name: statping-storage + persistentVolumeClaim: + claimName: statping-claim diff --git a/dev/portainer.json b/dev/portainer.json new file mode 100644 index 00000000..fe93ba18 --- /dev/null +++ b/dev/portainer.json @@ -0,0 +1,21 @@ +[ + { + "type": 1, + "title": "Statping", + "restart_policy": "unless-stopped", + "description": "Service monitoring with an easy to use status page and mobile app", + "logo": "https://assets.statping.com/icon.png", + "image": "statping/statping:latest", + "platform": "linux", + "categories": ["monitoring"], + "administrator_only": false, + "ports": [ + "8080:8080/tcp" + ], + "volumes": [ + { + "container": "/app" + } + ] + } +] diff --git a/dev/postman.json b/dev/postman.json index 1f6b4ba6..f21fd2b1 100644 --- a/dev/postman.json +++ b/dev/postman.json @@ -2,7 +2,7 @@ "info": { "_postman_id": "3c6a7841-0b39-4878-a3a6-1c76832b7679", "name": "Statping", - "description": "The Statping API allows you to programmatically access data on your Statping server.\n\nThe easiest way to get started with the API is by running your own Docker or local instance of the Statping server.\n\n\n\n# Authentication\n\nAn API Key is required to be sent as part of every request to the Statping API, by using the `Authorization` and the API Secret Key as a header.\n\n> If you do not have an API Secret Key, you can find it on the Settings page.\n\nYou can Authenticate by implementing the following...\n- Using the `Authorization` header with API Secret Key\n- Setting `GO_ENV` to `test` to bypass all authentication\n- Adding `?api=` URL Query along with the API Secret Key\n- Being logged into Statping as an admin (using JWT sessions/cookies)\n\n> Authentication will create a cookie named `statping_auth`.\n\n# Environment Variables\nStatping includes many environment variables that can give you more control over your instance. Please view the [Environment Variables Wiki](https://github.com/statping/statping/wiki/Environment-Variables) page to view a complete list. Below are a couple important ones...\n- `STATPING_DIR` - Statping's working directory. By default, this will be set to the current working directory. This path will contain the `config.yml` file, `logs`, and `assets` folder. \n- `SASS` - Absolute path to the `sass` executable. By default it will attempt to find `sass` in your `$PATH`. \n\n# Demo\nYou can checkout the Statping Demo instance at [https://demo.statping.com](https://demo.statping.com). All features are available for you to experiment with. The API Secret Key is `demoapisecret123`. Since this instance is public, it will be reset with sample data **every 90 minutes**.\n", + "description": "The Statping API allows you to programmatically access data on your Statping server.\n\nThe easiest way to get started with the API is by running your own Docker or local instance of the Statping server.\n\n\n\n# Authentication\n\nAn API Key is required to be sent as part of every request to the Statping API, by using the `Authorization` and the API Secret Key as a header.\n\n> If you do not have an API Secret Key, you can find it on the Settings page.\n\nYou can Authenticate by implementing the following...\n- Using the `Authorization` header with API Secret Key\n- Setting `GO_ENV` to `test` to bypass all authentication\n- Adding `?api=` URL Query along with the API Secret Key\n- Adding `?api=` URL Query along with the API Key for a user\n- Being logged into Statping as an admin (using JWT sessions/cookies)\n\n> Authentication will create a cookie named `statping_auth`.\n\n# Environment Variables\nStatping includes many environment variables that can give you more control over your instance. Please view the [Environment Variables Wiki](https://github.com/statping/statping/wiki/Environment-Variables) page to view a complete list. Below are a couple important ones...\n- `STATPING_DIR` - Statping's working directory. By default, this will be set to the current working directory. This path will contain the `config.yml` file, `logs`, and `assets` folder. \n- `SASS` - Absolute path to the `sass` executable. By default it will attempt to find `sass` in your `$PATH`. \n\n# Demo\nYou can checkout the Statping Demo instance at [https://demo.statping.com](https://demo.statping.com). All features are available for you to experiment with. The API Secret Key is `demoapisecret123`. Since this instance is public, it will be reset with sample data **every 90 minutes**.\n", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ @@ -786,6 +786,105 @@ "description": "You can create custom badges with dynamic information by using [Shields.io](https://shields.io/) and parsing JSON fields with [JSONPath](http://jsonpath.com/). \n\n## Examples\n\n#### Service Uptime Percent\n\n\n- URL: [https://demo.statping.com/api/services/2](https://demo.statping.com/api/services/2)\n- JSON Path: `$.online_24_hours`\n- Suffix: `%`\n\n```\nhttps://img.shields.io/badge/dynamic/json?color=blue&label=%20Statping%20Uptime&query=%24.online_24_hours&url=https%3A%2F%2Fdemo.statping.com%2Fapi%2Fservices%2F2&suffix=%\n```\n\n#### Count Services\n\n\n- URL: [https://demo.statping.com/health](https://demo.statping.com/health)\n- JSON Path: `$.services`\n- Suffix: ` services`\n\n```\nhttps://img.shields.io/badge/dynamic/json?color=purple&label=Demo%20Site&query=%24.services&url=https://demo.statping.com/health&suffix=%20services\n```" }, "response": [] + }, + { + "name": "Send Push Notification", + "event": [ + { + "listen": "test", + "script": { + "id": "11fe392f-3636-4d2d-84e9-1119b351d8ee", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"notifications\": [\n {\n \"tokens\": [\"dBLB1WvTJkiWl3ZPjP0-BS:APA91bGXUbKy65CaN1XqExHXZ892jik2k9XORXSiqdUyXhcQ5RDiJ6LfXrckuH3StYJFcma4UCDr_N038YUtxYsRIHYx_8vWZ6D2uq3199LegWXGl5tz-9zk3M4WZGX8WGxIRUJ31QtW\"],\n \"platform\": 2,\n \"message\": \"This notification will go to iOS and Android platform via Firebase!\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://push.statping.com/api/push", + "protocol": "https", + "host": [ + "push", + "statping", + "com" + ], + "path": [ + "api", + "push" + ] + }, + "description": "Send a push notification to the Statping mobile app using your Firebase device identifier." + }, + "response": [ + { + "name": "Send Push Notification", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"notifications\": [\n {\n \"tokens\": [\"dBLB1WvTJkiWl3ZPjP0-BS:APA91bGXUbKy65CaN1XqExHXZ892jik2k9XORXSiqdUyXhcQ5RDiJ6LfXrckuH3StYJFcma4UCDr_N038YUtxYsRIHYx_8vWZ6D2uq3199LegWXGl5tz-9zk3M4WZGX8WGxIRUJ31QtW\"],\n \"platform\": 2,\n \"message\": \"This notification will go to iOS and Android Statping App\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://push.statping.com/api/push", + "protocol": "https", + "host": [ + "push", + "statping", + "com" + ], + "path": [ + "api", + "push" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Length", + "value": "37" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Thu, 13 Aug 2020 02:24:17 GMT" + }, + { + "key": "X-Gorush-Version", + "value": "No Version Provided" + } + ], + "cookie": [], + "body": "{\n \"counts\": 1,\n \"logs\": [],\n \"success\": \"ok\"\n}" + } + ] } ], "description": "This is for Statping's miscellaneous API endpoints that aren't a part of another category.", @@ -840,7 +939,7 @@ "", "pm.test(\"View All Services\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.length).to.eql(5);", + " pm.expect(jsonData.length).to.eql(6);", "});" ], "type": "text/javascript" @@ -1714,7 +1813,7 @@ "services" ] }, - "description": "Create a new service and begin monitoring." + "description": "View a specific service, this will include the service's failures and checkins.\n\n#### Service Type Field\n- `http` - HTTP Service\n- `tcp` - TCP Service\n- `udp` - UDP Service\n- `icmp` - ICMP Service\n- `grpc` - gRPC Service\n- `static` - Static Service" }, "response": [ { @@ -1876,6 +1975,69 @@ } ] }, + { + "name": "Update Static Service", + "event": [ + { + "listen": "test", + "script": { + "id": "18cfae1e-4025-4338-a734-a552c8ac85ca", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Update Service\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.status).to.eql(\"success\");", + " pm.expect(jsonData.output.type).to.eql(\"static\");", + " pm.expect(jsonData.output.online).to.eql(false);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"online\": false,\n \"latency\": 30500,\n \"issue\": \"This is a failure string you can create\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{endpoint}}/api/services/7", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services", + "7" + ] + }, + "description": "Update a Static Service by setting it's online status to true or false. If false, you can include a issue string in the `issue` JSON field." + }, + "response": [] + }, { "name": "Delete Service Failures", "event": [ @@ -2026,7 +2188,7 @@ ] } ], - "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. ", + "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. \n\n### HTTP Services\nA HTTP service is a basic service that sends a HTTP request (GET, POST, PATCH, DELETE, etc) to check if that web service is online or not. You can expect a specific status code, and response body (including regex). \n\n### TCP and UDP Services\nTCP and UDP Services will send a request to the hostname and port of your choice.\n\n### ICMP Ping Services\nICMP Services will send a ICMP (ping) packet to your server to test if it's online.\n\n### gRPC Services\ngRPC Services will request your gRPC server and check the response\n\n### Static Services\nA Static Service is a \"fake\" service that is set online/offline by you.\n", "auth": { "type": "bearer", "bearer": [ @@ -2218,14 +2380,14 @@ } }, "url": { - "raw": "{{endpoint}}/api/services/1/incidents", + "raw": "{{endpoint}}/api/services/3/incidents", "host": [ "{{endpoint}}" ], "path": [ "api", "services", - "1", + "3", "incidents" ] }, @@ -2292,213 +2454,6 @@ } ] }, - { - "name": "Update Service Incident", - "event": [ - { - "listen": "test", - "script": { - "id": "42cd68de-5bd4-4d4e-b687-fa0bafc1d61a", - "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": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"title\": \"Updated Downtime\",\n\t\"description\": \"This is an update for an incident\"\n}" - }, - "url": { - "raw": "{{endpoint}}/api/incidents/1", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "incidents", - "1" - ] - }, - "description": "View all incidents for a single service." - }, - "response": [] - }, - { - "name": "Delete Incident", - "event": [ - { - "listen": "test", - "script": { - "id": "c6735dbe-86e8-4b42-9b04-6fc1fea949df", - "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": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "url": { - "raw": "{{endpoint}}/api/incidents/1", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "incidents", - "1" - ] - }, - "description": "View all incidents for a single service." - }, - "response": [ - { - "name": "Delete Incident", - "originalRequest": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "url": { - "raw": "{{endpoint}}/api/incidents/2", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "incidents", - "2" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Date", - "value": "Mon, 04 May 2020 03:31:48 GMT" - }, - { - "key": "Content-Length", - "value": "207" - }, - { - "key": "Connection", - "value": "close" - } - ], - "cookie": [], - "body": "{\n \"status\": \"success\",\n \"type\": \"incident\",\n \"method\": \"delete\",\n \"id\": 2,\n \"output\": {\n \"id\": 2,\n \"title\": \"Service Downtime\",\n \"service\": 1,\n \"created_at\": \"2020-05-04T03:18:24.818629Z\",\n \"updated_at\": \"2020-05-04T03:18:24.818629Z\"\n }\n}" - } - ] - }, - { - "name": "Incident Updates", - "event": [ - { - "listen": "test", - "script": { - "id": "e2e7d38f-efae-44d1-9361-cd301547feb3", - "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": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "url": { - "raw": "{{endpoint}}/api/incidents/2/updates", - "host": [ - "{{endpoint}}" - ], - "path": [ - "api", - "incidents", - "2", - "updates" - ] - }, - "description": "View all incidents for a single service." - }, - "response": [] - }, { "name": "Create Incident Update", "event": [ @@ -2609,6 +2564,113 @@ } ] }, + { + "name": "Incident Updates", + "event": [ + { + "listen": "test", + "script": { + "id": "e2e7d38f-efae-44d1-9361-cd301547feb3", + "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": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{endpoint}}/api/incidents/2/updates", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "incidents", + "2", + "updates" + ] + }, + "description": "View all incidents for a single service." + }, + "response": [] + }, + { + "name": "Update Service Incident", + "event": [ + { + "listen": "test", + "script": { + "id": "42cd68de-5bd4-4d4e-b687-fa0bafc1d61a", + "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": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"title\": \"Updated Downtime\",\n\t\"description\": \"This is an update for an incident\"\n}" + }, + "url": { + "raw": "{{endpoint}}/api/incidents/1", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "incidents", + "1" + ] + }, + "description": "View all incidents for a single service." + }, + "response": [] + }, { "name": "Delete Incident Update", "event": [ @@ -2712,6 +2774,106 @@ "body": "{\n \"status\": \"success\",\n \"type\": \"incident_update\",\n \"method\": \"delete\",\n \"id\": 1,\n \"output\": {\n \"id\": 1,\n \"message\": \"Website is loading very slowly, looking into this.\",\n \"type\": \"Investigating\",\n \"created_at\": \"2020-05-04T03:20:05.102435Z\",\n \"updated_at\": \"2020-05-04T03:20:05.102435Z\"\n }\n}" } ] + }, + { + "name": "Delete Incident", + "event": [ + { + "listen": "test", + "script": { + "id": "c6735dbe-86e8-4b42-9b04-6fc1fea949df", + "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": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{endpoint}}/api/incidents/1", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "incidents", + "1" + ] + }, + "description": "View all incidents for a single service." + }, + "response": [ + { + "name": "Delete Incident", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{endpoint}}/api/incidents/2", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "incidents", + "2" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Mon, 04 May 2020 03:31:48 GMT" + }, + { + "key": "Content-Length", + "value": "207" + }, + { + "key": "Connection", + "value": "close" + } + ], + "cookie": [], + "body": "{\n \"status\": \"success\",\n \"type\": \"incident\",\n \"method\": \"delete\",\n \"id\": 2,\n \"output\": {\n \"id\": 2,\n \"title\": \"Service Downtime\",\n \"service\": 1,\n \"created_at\": \"2020-05-04T03:18:24.818629Z\",\n \"updated_at\": \"2020-05-04T03:18:24.818629Z\"\n }\n}" + } + ] } ], "protocolProfileBehavior": {} @@ -3212,6 +3374,8 @@ "pm.test(\"Check Login JWT Token\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData).to.have.property('token');", + " pm.expect(jsonData).to.have.property('admin');", + " pm.globals.set(\"token\", jsonData.token);", "});" ], "type": "text/javascript" @@ -3308,24 +3472,141 @@ "_postman_previewlanguage": "json", "header": [ { - "key": "Content-Length", - "value": "174" + "key": "Content-Type", + "value": "application/json" }, + { + "key": "Set-Cookie", + "value": "statping_auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInNjb3BlcyI6ImFkbWluIiwiZXhwIjoxNTk2NzQzMDUzfQ.dQQGgUDhFEjCL2Gi-Seg0hBp_sqVsDn3cXB0GpSorJI; Path=/; Expires=Thu, 06 Aug 2020 19:44:13 GMT; Max-Age=259200" + }, + { + "key": "Date", + "value": "Mon, 03 Aug 2020 19:44:13 GMT" + }, + { + "key": "Content-Length", + "value": "197" + }, + { + "key": "Connection", + "value": "close" + } + ], + "cookie": [], + "body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInNjb3BlcyI6ImFkbWluIiwiZXhwIjoxNTk2NzQzMDUzfQ.dQQGgUDhFEjCL2Gi-Seg0hBp_sqVsDn3cXB0GpSorJI\",\n \"admin\": true\n}" + } + ] + }, + { + "name": "Check User Token", + "event": [ + { + "listen": "test", + "script": { + "id": "560e439b-d588-4a2f-a8a6-a0607531d74c", + "exec": [ + "pm.test(\"Response is ok\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"View Token Response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.username).to.eql(\"admin\");", + " pm.expect(jsonData.admin).to.eql(true);", + " pm.expect(jsonData.scopes).to.eql(\"admin\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{endpoint}}/api/users/token", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users", + "token" + ] + }, + "description": "Send your JWT token from login to this endpoint to return the JSON values." + }, + "response": [ + { + "name": "Check User Token", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{endpoint}}/api/users/token", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users", + "token" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Date", - "value": "Sat, 02 May 2020 00:56:17 GMT" + "value": "Mon, 03 Aug 2020 19:47:23 GMT" }, { - "key": "Set-Cookie", - "value": "statping_auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsImV4cCI6MTU4ODY0MDE3N30.tf399_LfAphSGlKMtgphg6qpPrn-_w92XfCrK5FwbZY; Expires=Tue, 05 May 2020 00:56:17 GMT" + "key": "Content-Length", + "value": "68" + }, + { + "key": "Connection", + "value": "close" } ], "cookie": [], - "body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsImV4cCI6MTU4ODY0MDE3N30.tf399_LfAphSGlKMtgphg6qpPrn-_w92XfCrK5FwbZY\",\n \"admin\": true\n}" + "body": "{\n \"username\": \"admin\",\n \"admin\": true,\n \"scopes\": \"admin\",\n \"exp\": 1596743053\n}" } ] }, @@ -3917,7 +4198,7 @@ "", "pm.test(\"View All Notifiers\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.length).to.eql(11);", + " pm.expect(jsonData.length).to.eql(13);", "});" ], "type": "text/javascript" @@ -4121,7 +4402,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"method\": \"slack\",\n \"host\": \"https://hooks.slack.com/services/EXAMPLEIDHERE/BV33WKP0C/MtKw3Kc8BFylTv4pohKqHtXX\",\n \"enabled\": true,\n \"limits\": 55\n}", + "raw": "{\n \"method\": \"slack\",\n \"host\": \"https://hooks.slack.com/services/TTJ1B90DP/RENU20O9M/9uI823SUnYBuGcxYlpSimD6H\",\n \"enabled\": true,\n \"limits\": 55\n}", "options": { "raw": {} } @@ -4233,7 +4514,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.DowntimeAgo}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}", + "raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.Downtime.Human}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}", "options": { "raw": {} } @@ -4911,8 +5192,7 @@ " var first = jsonData[0];", " var id = pm.globals.get(\"checkin_id\");", " pm.expect(first.name).to.eql(\"Demo Checkin 1\");", - " pm.expect(first.grace).to.eql(300);", - " pm.expect(first.interval).to.eql(300);", + " pm.expect(first.interval).to.eql(3);", "});" ], "type": "text/javascript" @@ -4990,8 +5270,7 @@ " 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);", + " pm.expect(jsonData.output.interval).to.eql(3);", " var id = jsonData.output.api_key;", " pm.globals.set(\"checkin_id\", id);", "});" @@ -5022,7 +5301,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 900,\n \"grace\": 60\n}", + "raw": "{\n \"service_id\": 2,\n \"name\": \"Server Checkin\",\n \"interval\": 3\n}", "options": { "raw": {} } @@ -5182,8 +5461,7 @@ " var id = pm.globals.get(\"checkin_id\");", " pm.expect(jsonData.name).to.eql(\"Server Checkin\");", " pm.expect(jsonData.api_key).to.eql(id);", - " pm.expect(jsonData.grace).to.eql(60);", - " pm.expect(jsonData.interval).to.eql(900);", + " pm.expect(jsonData.interval).to.eql(3);", "});" ], "type": "text/javascript" diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index f181627b..0e10e4a4 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -60,6 +60,9 @@ const webpackConfig = merge(commonConfig, { plugins: [ new webpack.EnvironmentPlugin(environment), new CleanWebpackPlugin(), + // new webpack.optimize.LimitChunkCountPlugin({ + // maxChunks: 1 + // }), new MiniCSSExtractPlugin({ filename: 'css/[name].css', chunkFilename: 'css/[name].css' @@ -71,7 +74,7 @@ const webpackConfig = merge(commonConfig, { threshold: 10240, minRatio: 0.8 }), - new webpack.HashedModuleIdsPlugin(), + // new webpack.HashedModuleIdsPlugin(), new HtmlPlugin({ template: 'public/base.gohtml', filename: 'base.gohtml', diff --git a/frontend/package.json b/frontend/package.json index c73a280a..46b06aac 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,26 +19,25 @@ "@fortawesome/free-brands-svg-icons": "^5.12.1", "@fortawesome/free-solid-svg-icons": "^5.12.0", "@fortawesome/vue-fontawesome": "^0.1.9", - "@sentry/browser": "^5.13.2", - "@sentry/integrations": "^5.13.2", - "apexcharts": "^3.15.0", + "@sentry/browser": "^5.20.1", + "@sentry/integrations": "^5.20.1", + "apexcharts": "^3.6.6", "axios": "^0.19.1", - "bootstrap": "^4.4.1", - "bootstrap-vue": "^2.6.1", "codemirror-colorpicker": "^1.9.66", - "core-js": "^3.4.4", + "core-js": "^3.6.5", "date-fns": "^2.9.0", "js-beautify": "^1.11.0", "querystring": "^0.2.0", + "sass": "^1.26.10", + "semver": "^7.3.2", "vue": "^2.6.11", - "vue-apexcharts": "^1.5.2", + "vue-apexcharts": "^1.6.0", "vue-clipboard2": "^0.3.1", "vue-codemirror": "^4.0.6", "vue-cookies": "^1.7.0", "vue-flatpickr-component": "^8.1.5", "vue-github-button": "^1.1.2", "vue-i18n": "^8.18.1", - "vue-moment": "^4.1.0", "vue-observe-visibility": "^0.4.6", "vue-router": "~3.0", "vuedraggable": "^2.23.2", @@ -64,7 +63,6 @@ "compression-webpack-plugin": "~2.0", "cross-env": "^7.0.2", "css-loader": "~2.1", - "cypress": "^4.3.0", "eslint": "~5.16", "eslint-config-standard": "~10.2", "eslint-friendly-formatter": "~3.0", @@ -78,13 +76,13 @@ "expect": "^25.1.0", "file-loader": "^5.0.2", "friendly-errors-webpack-plugin": "~1.7", + "github-wikito-converter": "^1.5.2", "html-webpack-plugin": "^4.0.0-beta.11", "jsdom": "^16.2.0", "jsdom-global": "^3.0.2", "mini-css-extract-plugin": "~0.5", "mocha": "^7.0.1", "mochapack": "^1.1.13", - "node-sass": "^4.13.1", "optimize-css-assets-webpack-plugin": "~5.0", "sass-loader": "^8.0.2", "start-server-and-test": "^1.10.11", diff --git a/frontend/public/base.gohtml b/frontend/public/base.gohtml index 07bf22ba..45ecbe3c 100644 --- a/frontend/public/base.gohtml +++ b/frontend/public/base.gohtml @@ -43,19 +43,11 @@ {{if USE_CDN}} - - - - {{else}} - {{if USING_ASSETS}} - - - + {{else}} <% _.each(htmlWebpackPlugin.tags.headTags, function(headTag) { %> <%= headTag %> <% }) %> {{end}} - {{end}}