mirror of https://github.com/statping/statping
commit
d4367cc9c9
|
@ -18,3 +18,14 @@ dev
|
|||
config.yml
|
||||
*.db
|
||||
tmp
|
||||
frontend/node_modules
|
||||
.next
|
||||
node_modules
|
||||
Dockerfile
|
||||
Dockerfile.base
|
||||
!dev/dev-env.sh
|
||||
!dev/modd.conf
|
||||
!dev/grafana
|
||||
!dev/prometheus.yml
|
||||
docker
|
||||
statping.db
|
||||
|
|
|
@ -4,20 +4,20 @@ about: If you're having an issue or see an error
|
|||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
### Describe the bug
|
||||
Try to explain what issue your'e having in detail. You can copy and paste the issue from the log file in your root directory `/logs/statup.log`.
|
||||
|
||||
**To Reproduce**
|
||||
### To Reproduce
|
||||
Steps to reproduce the behavior:
|
||||
1. I'm using version: '...'
|
||||
2. I went to '....'
|
||||
3. Press this button '....'
|
||||
4. And things did '....'
|
||||
|
||||
**Expected Behavior**
|
||||
### Expected Behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
### Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
[](https://gitter.im/statup-app/general) [](https://github.com/hunterlong/statping/releases/latest) [](https://travis-ci.com/hunterlong/statup)
|
||||
[](https://slack.statping.com/) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statping)
|
||||
|
|
|
@ -4,19 +4,19 @@ about: Suggest a feature and let's see what others say
|
|||
|
||||
---
|
||||
|
||||
**What would you like on Statping?**
|
||||
### What would you like on Statping?
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
### Describe the solution you'd like
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
### Describe alternatives you've considered
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
### Is your feature request related to a problem? Please describe.
|
||||
I'm always frustrated when [...]
|
||||
|
||||
**Additional context**
|
||||
### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
[](https://gitter.im/statup-app/general) [](https://github.com/hunterlong/statping/releases/latest) [](https://travis-ci.com/hunterlong/statup)
|
||||
[](https://slack.statping.com/) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statping)
|
||||
|
|
|
@ -4,20 +4,20 @@ about: If you're having an issue or see an error
|
|||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
### To Reproduce
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
### Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
### Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
[](https://gitter.im/statup-app/general) [](https://github.com/hunterlong/statping/releases/latest) [](https://travis-ci.com/hunterlong/statup)
|
||||
[](https://gitter.im/statup-app/general) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statup)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
on: [push, pull_request]
|
||||
name: Golang Test
|
||||
jobs:
|
||||
test:
|
||||
env:
|
||||
GOPATH: ${{ github.workspace }}
|
||||
GO111MODULE: on
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.14.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./src/github.com/${{ github.repository }}
|
||||
- name: Go Mod
|
||||
run: go mod download
|
||||
- name: Test
|
||||
run: go test -p=1 ./...
|
|
@ -0,0 +1,22 @@
|
|||
name: ReleaseWorkflow
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published, prereleased]
|
||||
|
||||
jobs:
|
||||
createSentryRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
- name: Create a Sentry.io 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: golang
|
||||
with:
|
||||
tagName: ${{ github.ref }}
|
||||
environment: qa
|
|
@ -0,0 +1,14 @@
|
|||
on: push
|
||||
name: Slack Notification
|
||||
jobs:
|
||||
slackNotification:
|
||||
name: Slack Notification
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/action-slack-notify@v2.0.0
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_URL }}
|
||||
SLACK_CHANNEL: dev
|
||||
SLACK_USERNAME: Github
|
|
@ -14,12 +14,11 @@ dev/plugin/*.so
|
|||
html/scss/.sass-cache
|
||||
dart-sass
|
||||
.sass-cache
|
||||
public
|
||||
assets
|
||||
*.log
|
||||
.env
|
||||
logs
|
||||
tmp
|
||||
source/dist
|
||||
/dev/test/node_modules
|
||||
dev/test/cypress/videos
|
||||
dev/test/cypress/screenshots
|
||||
|
@ -29,3 +28,8 @@ sass
|
|||
.DS_Store
|
||||
source/css/base.css.map
|
||||
dev/test/node_modules
|
||||
source/scss
|
||||
databases
|
||||
statping
|
||||
docker
|
||||
tmp
|
||||
|
|
|
@ -2,13 +2,13 @@ os:
|
|||
- linux
|
||||
language: go
|
||||
go: 1.13.5
|
||||
go_import_path: github.com/hunterlong/statping
|
||||
go_import_path: github.com/statping/statping
|
||||
cache:
|
||||
directories:
|
||||
- "~/.npm"
|
||||
- "~/.cache"
|
||||
- "$GOPATH/src/github.com/hunterlong/statping/tmp"
|
||||
- "$GOPATH/src/github.com/hunterlong/statping/vendor"
|
||||
- "$GOPATH/src/github.com/statping/statping/tmp"
|
||||
- "$GOPATH/src/github.com/statping/statping/vendor"
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
|
@ -23,7 +23,7 @@ env:
|
|||
- DB_PASS=
|
||||
- DB_DATABASE=test
|
||||
- GO_ENV=test
|
||||
- STATPING_DIR=$GOPATH/src/github.com/hunterlong/statping
|
||||
- STATPING_DIR=$GOPATH/src/github.com/statping/statping
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
|
|
31
Dockerfile
31
Dockerfile
|
@ -1,33 +1,22 @@
|
|||
FROM golang:1.13.5-alpine as base
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
ARG VERSION
|
||||
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
WORKDIR /go/src/github.com/hunterlong/statping
|
||||
ADD Makefile go.mod /go/src/github.com/hunterlong/statping/
|
||||
RUN go mod vendor && \
|
||||
make dev-deps
|
||||
ADD . /go/src/github.com/hunterlong/statping
|
||||
RUN make install
|
||||
FROM statping/statping:base AS base
|
||||
|
||||
# Statping :latest Docker Image
|
||||
# Statping main Docker image that contains all required libraries
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
RUN apk --no-cache add libgcc libstdc++ curl jq
|
||||
|
||||
COPY --from=base /go/bin/statping /usr/local/bin/
|
||||
COPY --from=base /usr/local/bin/sass /usr/local/bin/
|
||||
COPY --from=base /usr/local/share/ca-certificates /usr/local/share/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG VERSION
|
||||
ENV IS_DOCKER=true
|
||||
ENV STATPING_DIR=/app
|
||||
ENV PORT=8080
|
||||
RUN apk --no-cache add curl jq libsass
|
||||
|
||||
COPY --from=base /usr/local/bin/sass /usr/local/bin/sass
|
||||
COPY --from=base /go/bin/statping /usr/local/bin/statping
|
||||
|
||||
WORKDIR /app
|
||||
VOLUME /app
|
||||
EXPOSE $PORT
|
||||
|
||||
HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD curl -s "http://localhost:$PORT/health" | jq -r -e ".online==true"
|
||||
|
||||
CMD statping -port $PORT
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
FROM node:10.17.0 AS frontend
|
||||
RUN npm install yarn -g
|
||||
WORKDIR /statping
|
||||
COPY ./frontend/package.json .
|
||||
COPY ./frontend/yarn.lock .
|
||||
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
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
WORKDIR /go/src/github.com/statping/statping
|
||||
ADD go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ENV GO111MODULE on
|
||||
RUN go get github.com/stretchr/testify/assert && \
|
||||
go get github.com/stretchr/testify/require && \
|
||||
go get github.com/GeertJohan/go.rice/rice && \
|
||||
go get github.com/cortesi/modd/cmd/modd && \
|
||||
go get github.com/crazy-max/xgo
|
||||
COPY . .
|
||||
COPY --from=frontend /statping/dist/ ./source/dist/
|
||||
RUN make clean frontend-copy generate embed build
|
||||
RUN chmod a+x statping && mv statping /go/bin/statping
|
||||
# /go/bin/statping - statping binary
|
||||
# /usr/local/bin/sass - sass binary
|
||||
# /statping - Vue frontend (from frontend)
|
413
Makefile
413
Makefile
|
@ -2,248 +2,131 @@ VERSION=$(shell cat version.txt)
|
|||
SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278
|
||||
BINARY_NAME=statping
|
||||
GOBUILD=go build -a
|
||||
GOVERSION=1.13.5
|
||||
GOVERSION=1.14
|
||||
XGO=xgo -go $(GOVERSION) --dest=build
|
||||
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)"
|
||||
TRVIS_SECRET=lRqWSt5BoekFK6+padJF+b77YkGdispPXEUKNuD7/Hxb7yJMoI8T/n8xZrTHtCZPdjtpy7wIlJCezNoYEZB3l2GnD6Y1QEZEXF7MIxP7hwsB/uSc5/lgdGW0ZLvTBfv6lwI/GjQIklPBW/4xcKJtj4s1YBP7xvqyIb/lDN7TiOqAKF4gqRVVfsxvlkm7j4TiPCXtz17hYQfU8kKBbd+vd3PuZgdWqs//5RwKk3Ld8QR8zoo9xXQVC5NthiyVbHznzczBsHy2cRZZoWxyi7eJM1HrDw8Jn/ivJONIHNv3RgFVn2rAoKu1X8F6FyuvPO0D2hWC62mdO/e0kt4X0mn9/6xlLSKwrHir67UgNVQe3tvlH0xNKh+yNZqR5x9t0V54vNks6Pgbhas5EfLHoWn5cF4kbJzqkXeHjt1msrsqpA3HKbmtwwjJr4Slotfiu22mAhqLSOV+xWV+IxrcNnrEq/Pa+JAzU12Uyxs8swaLJGPRAlWnJwzL9HK5aOpN0sGTuSEsTwj0WxeMMRx25YEq3+LZOgwOy3fvezmeDnKuBZa6MVCoMMpx1CRxMqAOlTGZXHjj+ZPmqDUUBpzAsFSzIdVRgcnDlLy7YRiz3tVWa1G5S07l/VcBN7ZgvCwOWZ0QgOH0MxkoDfhrfoMhNO6MBFDTRKCEl4TroPEhcInmXU8=
|
||||
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": { "os": [ "linux" ], "language": "go", "go": [ "${GOVERSION}" ], "go_import_path": "github.com/hunterlong/statping", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "${VERSION}", "secure": "${TRVIS_SECRET}" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "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": "$$TAG_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": {"branch": "master"} } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull crazymax/xgo:$(GOVERSION)", "make release" ], "after_success": [], "after_deploy": [ "make publish-homebrew" ] } } }'
|
||||
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statping
|
||||
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "os": [ "linux" ], "language": "go", "go": [ "${GOVERSION}" ], "go_import_path": "github.com/statping/statping", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "${VERSION}", "secure": "${TRVIS_SECRET}" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "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": "$$TAG_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": {"branch": "master"} } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull crazymax/xgo:$(GOVERSION)", "make release" ], "after_success": [], "after_deploy": [ "make publish-homebrew" ] } } }'
|
||||
TEST_DIR=$(GOPATH)/src/github.com/statping/statping
|
||||
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
|
||||
|
||||
# build all arch's and release Statping
|
||||
release: dev-deps
|
||||
wget -O statping.gpg $(SIGN_URL)
|
||||
gpg --import statping.gpg
|
||||
make build-all
|
||||
all: clean yarn-install compile docker-base docker-vue build-all compress
|
||||
|
||||
# build and push the images to docker hub
|
||||
docker: docker-build-all docker-publish-all
|
||||
up:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans
|
||||
make print_details
|
||||
|
||||
# test all versions of Statping, golang testing and then cypress UI testing
|
||||
test-all: dev-deps test
|
||||
down:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml down --volumes --remove-orphans
|
||||
|
||||
# test all versions of Statping, golang testing and then cypress UI testing
|
||||
test-ui: dev-deps docker-build-dev cypress-test
|
||||
lite: clean
|
||||
docker build -t hunterlong/statping:dev -f dev/Dockerfile.dev .
|
||||
docker-compose -f dev/docker-compose.lite.yml down
|
||||
docker-compose -f dev/docker-compose.lite.yml up --remove-orphans
|
||||
|
||||
# testing to be ran on travis ci
|
||||
travis-test: dev-deps cypress-install test coverage
|
||||
reup: down clean compose-build-full up
|
||||
|
||||
# build and compile all arch's for Statping
|
||||
build-all: build-mac build-linux build-windows build-alpine compress
|
||||
test: clean
|
||||
go test -v -p=4 -ldflags="-X main.VERSION=testing" -coverprofile=coverage.out ./...
|
||||
|
||||
# build all docker tags
|
||||
docker-build-all: docker-build-latest
|
||||
test-ci: clean compile test-deps
|
||||
SASS=`which sass` STATPING_DIR=${GOPATH}/src/github.com/statping/statping go test -v -covermode=count -coverprofile=coverage.out -p=4 ./...
|
||||
goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
# push all docker tags built
|
||||
docker-publish-all: docker-push-latest
|
||||
|
||||
snapcraft: clean snapcraft-build snapcraft-release
|
||||
|
||||
# build Statping for local arch
|
||||
build: compile
|
||||
go mod vendor
|
||||
$(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd
|
||||
|
||||
# build Statping plugins
|
||||
build-plugin:
|
||||
$(GOBUILD) $(BUILDVERSION) -buildmode=plugin -o ./dev/plugin/example.so -v ./dev/plugin
|
||||
|
||||
test-plugin: clean
|
||||
mkdir plugins
|
||||
$(GOBUILD) $(BUILDVERSION) -buildmode=plugin -o ./dev/plugin/example.so -v ./dev/plugin
|
||||
mv ./dev/plugin/example.so ./plugins/example.so
|
||||
STATPING_DIR=$(TEST_DIR) go test -v -p=1 $(BUILDVERSION) -coverprofile=coverage.out ./plugin
|
||||
|
||||
# build Statping debug app
|
||||
build-debug: compile
|
||||
$(GOBUILD) $(BUILDVERSION) -tags debug -o $(BINARY_NAME) -v ./cmd
|
||||
|
||||
# install Statping for local arch and move binary to gopath/src/bin/statping
|
||||
install: build
|
||||
mv $(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME)
|
||||
$(GOPATH)/bin/$(BINARY_NAME) version
|
||||
|
||||
# run Statping from local arch
|
||||
run: build
|
||||
./$(BINARY_NAME) --ip 0.0.0.0 --port 8080
|
||||
|
||||
# run Statping with Delve for debugging
|
||||
rundlv:
|
||||
lsof -ti:8080 | xargs kill
|
||||
DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none GO_ENV=test \
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./statping
|
||||
|
||||
killdlv:
|
||||
lsof -ti:2345 | xargs kill
|
||||
|
||||
builddlv:
|
||||
$(GOBUILD) -gcflags "all=-N -l" -o ./$(BINARY_NAME) -v ./cmd
|
||||
|
||||
watch:
|
||||
find . -print | grep -i '.*\.\(go\|gohtml\)' | justrun -v -c \
|
||||
'go build -v -gcflags "all=-N -l" -o statping ./cmd && make rundlv &' \
|
||||
-delay 10s -stdin \
|
||||
-i="Makefile,statping,statup.db,statup.db-journal,handlers/graphql/generated.go"
|
||||
|
||||
# compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go
|
||||
compile: generate
|
||||
sass source/scss/base.scss source/css/base.css
|
||||
cd source && rice embed-go
|
||||
rm -rf .sass-cache
|
||||
|
||||
# benchmark testing
|
||||
benchmark:
|
||||
cd handlers && go test -v -run=^$ -bench=. -benchtime=5s -memprofile=prof.mem -cpuprofile=prof.cpu
|
||||
|
||||
# view benchmark testing using pprof
|
||||
benchmark-view:
|
||||
go tool pprof handlers/handlers.test handlers/prof.cpu > top20
|
||||
|
||||
# test Statping golang tetsing files
|
||||
test: clean compile install build-plugin
|
||||
STATPING_DIR=$(TEST_DIR) go test -v -p=1 $(BUILDVERSION) -coverprofile=coverage.out ./...
|
||||
gocov convert coverage.out > coverage.json
|
||||
|
||||
test-api:
|
||||
DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none statping &
|
||||
sleep 300 && newman run source/tmpl/postman.json -e dev/postman_environment.json --delay-request 500
|
||||
|
||||
# report coverage to Coveralls
|
||||
coverage:
|
||||
$(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS)
|
||||
|
||||
# generate documentation for Statping functions
|
||||
docs:
|
||||
rm -f dev/README.md
|
||||
printf "# Statping Dev Documentation\n" > dev/README.md
|
||||
printf "This readme is automatically generated from the Golang documentation. [](https://godoc.org/github.com/hunterlong/statping)\n\n" > dev/README.md
|
||||
godocdown github.com/hunterlong/statping >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/cmd >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/core >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/handlers >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/notifiers >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/plugin >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/source >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/types >> dev/README.md
|
||||
godocdown github.com/hunterlong/statping/utils >> dev/README.md
|
||||
gocov-html coverage.json > dev/COVERAGE.html
|
||||
revive -formatter stylish > dev/LINT.md
|
||||
|
||||
#
|
||||
# Build binary for Statping
|
||||
#
|
||||
|
||||
# build Statping for Mac, 64 and 32 bit
|
||||
build-mac: compile
|
||||
mkdir build
|
||||
$(XGO) $(BUILDVERSION) --targets=darwin/amd64,darwin/386 ./cmd
|
||||
|
||||
# build Statping for Linux 64, 32 bit, arm6/arm7
|
||||
build-linux: compile
|
||||
$(XGO) $(BUILDVERSION) --targets=linux/amd64,linux/386,linux/arm-7,linux/arm-6,linux/arm64 ./cmd
|
||||
|
||||
# build for windows 64 bit only
|
||||
build-windows: compile
|
||||
$(XGO) $(BUILDVERSION) --targets=windows-6.0/amd64 ./cmd
|
||||
|
||||
# build Alpine linux binary (used in docker images)
|
||||
build-alpine: compile
|
||||
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT) -linkmode external -extldflags -static" -out alpine ./cmd
|
||||
|
||||
#
|
||||
# Docker Makefile commands
|
||||
#
|
||||
|
||||
docker-test:
|
||||
docker-compose -f docker-compose.test.yml -p statping build
|
||||
docker-compose -f docker-compose.test.yml -p statping up -d
|
||||
docker logs -f statping_sut_1
|
||||
docker wait statping_sut_1
|
||||
|
||||
# build :latest docker tag
|
||||
docker-build-latest:
|
||||
docker build --build-arg VERSION=${VERSION} -t hunterlong/statping:latest --no-cache -f Dockerfile .
|
||||
docker tag hunterlong/statping:latest hunterlong/statping:v${VERSION}
|
||||
|
||||
# build :dev docker tag
|
||||
docker-build-dev:
|
||||
docker build --build-arg VERSION=${VERSION} -t hunterlong/statping:latest --no-cache -f Dockerfile .
|
||||
docker tag hunterlong/statping:dev hunterlong/statping:dev-v${VERSION}
|
||||
|
||||
# build Cypress UI testing :cypress docker tag
|
||||
docker-build-cypress: clean
|
||||
GOPATH=$(GOPATH) xgo -out statping -go $(GOVERSION) -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" --targets=linux/amd64 ./cmd
|
||||
docker build -t hunterlong/statping:cypress -f dev/Dockerfile-cypress .
|
||||
rm -f statping
|
||||
|
||||
# run hunterlong/statping:latest docker image
|
||||
docker-run: docker-build-latest
|
||||
docker run -it -p 8080:8080 hunterlong/statping:latest
|
||||
|
||||
# run hunterlong/statping:dev docker image
|
||||
docker-run-dev: docker-build-dev
|
||||
docker run -t -p 8080:8080 hunterlong/statping:dev
|
||||
|
||||
# run Cypress UI testing, hunterlong/statping:cypress docker image
|
||||
docker-run-cypress: docker-build-cypress
|
||||
docker run -t hunterlong/statping:cypress
|
||||
|
||||
# push the :base and :base-v{VERSION} tag to Docker hub
|
||||
docker-push-base:
|
||||
docker tag hunterlong/statping:base hunterlong/statping:base-v${VERSION}
|
||||
docker push hunterlong/statping:base
|
||||
docker push hunterlong/statping:base-v${VERSION}
|
||||
|
||||
# push the :dev tag to Docker hub
|
||||
docker-push-dev:
|
||||
docker push hunterlong/statping:dev
|
||||
docker push hunterlong/statping:dev-v${VERSION}
|
||||
|
||||
# push the :cypress tag to Docker hub
|
||||
docker-push-cypress:
|
||||
docker push hunterlong/statping:cypress
|
||||
|
||||
# push the :latest tag to Docker hub
|
||||
docker-push-latest:
|
||||
docker tag hunterlong/statping hunterlong/statping:dev
|
||||
docker push hunterlong/statping:latest
|
||||
docker push hunterlong/statping:dev
|
||||
docker push hunterlong/statping:v${VERSION}
|
||||
|
||||
docker-run-mssql:
|
||||
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=PaSsW0rD123' -p 1433:1433 -d microsoft/mssql-server-linux
|
||||
|
||||
# create Postgres, and MySQL instance using Docker (used for testing)
|
||||
databases:
|
||||
docker run --name statping_postgres -p 5432:5432 -e POSTGRES_PASSWORD=password123 -e POSTGRES_USER=root -e POSTGRES_DB=root -d postgres
|
||||
docker run --name statping_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password123 -e MYSQL_DATABASE=root -d mysql
|
||||
sleep 30
|
||||
|
||||
# install all required golang dependecies
|
||||
dev-deps:
|
||||
go get github.com/stretchr/testify/assert
|
||||
test-deps:
|
||||
go get golang.org/x/tools/cmd/cover
|
||||
go get github.com/mattn/goveralls
|
||||
go install github.com/mattn/goveralls
|
||||
go get github.com/rendon/testcli
|
||||
go get github.com/robertkrimen/godocdown/godocdown
|
||||
go get github.com/crazy-max/xgo
|
||||
go get github.com/GeertJohan/go.rice
|
||||
go get github.com/GeertJohan/go.rice/rice
|
||||
go install github.com/GeertJohan/go.rice/rice
|
||||
go get github.com/axw/gocov/gocov
|
||||
go get github.com/matm/gocov-html
|
||||
go get github.com/fatih/structs
|
||||
go get github.com/ararog/timeago
|
||||
go get gopkg.in/natefinch/lumberjack.v2
|
||||
go get golang.org/x/crypto/bcrypt
|
||||
|
||||
yarn-serve:
|
||||
cd frontend && yarn serve
|
||||
|
||||
yarn-install:
|
||||
cd frontend && rm -rf node_modules && yarn
|
||||
|
||||
go-run:
|
||||
go run ./cmd
|
||||
|
||||
start:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml start
|
||||
|
||||
stop:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml stop
|
||||
|
||||
logs:
|
||||
docker logs statping --follow
|
||||
|
||||
db-up:
|
||||
docker-compose -f dev/docker-compose.db.yml up -d --remove-orphans
|
||||
|
||||
db-down:
|
||||
docker-compose -f dev/docker-compose.full.yml down --remove-orphans
|
||||
|
||||
console:
|
||||
docker exec -t -i statping /bin/sh
|
||||
|
||||
compose-build-full: docker-base
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml build --parallel --build-arg VERSION=${VERSION}
|
||||
|
||||
docker-base:
|
||||
docker build -t statping/statping:base -f Dockerfile.base --build-arg VERSION=${VERSION} .
|
||||
|
||||
docker-latest: docker-base
|
||||
docker build -t statping/statping:latest --build-arg VERSION=${VERSION} .
|
||||
|
||||
docker-vue:
|
||||
docker build -t statping/statping:vue --build-arg VERSION=${VERSION} .
|
||||
|
||||
docker-test:
|
||||
docker-compose -f docker-compose.test.yml up --remove-orphans
|
||||
|
||||
push-base: clean compile docker-base
|
||||
docker push statping/statping:base
|
||||
|
||||
push-vue: clean compile docker-base docker-vue
|
||||
docker push statping/statping:base
|
||||
docker push statping/statping:vue
|
||||
|
||||
modd:
|
||||
modd -f ./dev/modd.conf
|
||||
|
||||
top:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml top
|
||||
|
||||
frontend-build:
|
||||
rm -rf source/dist && rm -rf frontend/dist
|
||||
cd frontend && yarn build
|
||||
cp -r frontend/dist source/ && cp -r frontend/src/assets/scss source/dist/
|
||||
cp -r source/tmpl/*.* source/dist/
|
||||
|
||||
frontend-copy:
|
||||
cp -r source/tmpl/*.* source/dist/
|
||||
|
||||
# compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go
|
||||
compile: generate frontend-build
|
||||
rm -f source/rice-box.go
|
||||
cd source && rice embed-go
|
||||
|
||||
embed:
|
||||
cd source && rice embed-go
|
||||
|
||||
build:
|
||||
$(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) ./cmd
|
||||
|
||||
install: build
|
||||
mv $(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME)
|
||||
|
||||
generate:
|
||||
cd source && go generate
|
||||
|
||||
# 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}
|
||||
|
@ -259,18 +142,54 @@ clean:
|
|||
find . -name "*.out" -type f -delete
|
||||
find . -name "*.cpu" -type f -delete
|
||||
find . -name "*.mem" -type f -delete
|
||||
rm -rf build
|
||||
rm -rf {build,tmp}
|
||||
|
||||
# tag version using git
|
||||
tag:
|
||||
git tag v${VERSION} --force
|
||||
print_details:
|
||||
@echo \==== Statping Development Instance ====
|
||||
@echo \Statping Vue Frontend: http://localhost:8888
|
||||
@echo \Statping Backend API: http://localhost:8585
|
||||
@echo \==== Statping Instances ====
|
||||
@echo \Statping SQLite: http://localhost:4000
|
||||
@echo \Statping MySQL: http://localhost:4005
|
||||
@echo \Statping Postgres: http://localhost:4010
|
||||
@echo \==== Databases ====
|
||||
@echo \PHPMyAdmin: http://localhost:6000 \(MySQL database management\)
|
||||
@echo \SQLite Web: http://localhost:6050 \(SQLite database management\)
|
||||
@echo \PGAdmin: http://localhost:7000 \(Postgres database management \| email: admin@admin.com password: admin\)
|
||||
@echo \Prometheus: http://localhost:7050 \(Prometheus Web UI\)
|
||||
@echo \==== Monitoring and IDE ====
|
||||
@echo \Grafana: http://localhost:3000 \(username: admin, password: admin\)
|
||||
|
||||
generate:
|
||||
cd source && go generate
|
||||
cd handlers/graphql && go generate
|
||||
build-all: xgo-install build-mac build-linux build-windows build-linux build-alpine compress
|
||||
|
||||
download-key:
|
||||
wget -O statping.gpg $(KEY_URL)
|
||||
gpg --import statping.gpg
|
||||
|
||||
# build Statping for Mac, 64 and 32 bit
|
||||
build-mac:
|
||||
mkdir build
|
||||
$(XGO) $(BUILDVERSION) --targets=darwin/amd64,darwin/386 ./cmd
|
||||
|
||||
# build Statping for Linux 64, 32 bit, arm6/arm7
|
||||
build-linux:
|
||||
$(XGO) $(BUILDVERSION) --targets=linux/amd64,linux/386,linux/arm-7,linux/arm-6,linux/arm64 ./cmd
|
||||
|
||||
# build for windows 64 bit only
|
||||
build-windows:
|
||||
$(XGO) $(BUILDVERSION) --targets=windows-6.0/amd64 ./cmd
|
||||
|
||||
# build Alpine linux binary (used in docker images)
|
||||
build-alpine:
|
||||
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT) -linkmode external -extldflags -static" -out alpine ./cmd
|
||||
|
||||
# build :latest docker tag
|
||||
docker-build-latest:
|
||||
docker build --build-arg VERSION=${VERSION} -t hunterlong/statping:latest --no-cache -f Dockerfile .
|
||||
docker tag hunterlong/statping:latest hunterlong/statping:v${VERSION}
|
||||
|
||||
# compress built binaries into tar.gz and zip formats
|
||||
compress:
|
||||
compress: download-key
|
||||
cd build && mv alpine-linux-amd64 $(BINARY_NAME)
|
||||
cd build && gpg --default-key $(SIGN_KEY) --batch --detach-sign --output statping.asc --armor $(BINARY_NAME)
|
||||
cd build && tar -czvf $(BINARY_NAME)-linux-alpine.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
||||
|
@ -307,14 +226,6 @@ publish-dev:
|
|||
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/hunterlong%2Fhomebrew-statping/requests
|
||||
|
||||
# install NPM reuqirements for cypress testing
|
||||
cypress-install:
|
||||
cd dev/test && npm install
|
||||
|
||||
# run Cypress UI testing
|
||||
cypress-test: clean cypress-install
|
||||
cd dev/test && npm test
|
||||
|
||||
upload_to_s3:
|
||||
aws s3 cp ./source/css $(ASSETS_BKT) --recursive --exclude "*" --include "*.css"
|
||||
aws s3 cp ./source/js $(ASSETS_BKT) --recursive --exclude "*" --include "*.js"
|
||||
|
@ -331,27 +242,6 @@ travis-build: travis_s3_creds upload_to_s3
|
|||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(TRAVIS_BUILD_CMD) https://api.travis-ci.com/repo/hunterlong%2Fstatping/requests
|
||||
curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER)
|
||||
|
||||
snapcraft-build: build-all
|
||||
PWD=$(shell pwd)
|
||||
cp build/$(BINARY_NAME)-linux-x64.tar.gz build/$(BINARY_NAME)-linux.tar.gz
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}:/build -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=amd64"
|
||||
cp build/$(BINARY_NAME)-linux-x32.tar.gz build/$(BINARY_NAME)-linux.tar.gz
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}:/build -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=i386"
|
||||
cp build/$(BINARY_NAME)-linux-arm64.tar.gz build/$(BINARY_NAME)-linux.tar.gz
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}:/build -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=arm64"
|
||||
cp build/$(BINARY_NAME)-linux-arm7.tar.gz build/$(BINARY_NAME)-linux.tar.gz
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}:/build -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=armhf"
|
||||
rm -f build/$(BINARY_NAME)-linux.tar.gz
|
||||
|
||||
snapcraft-release:
|
||||
snapcraft push statping_${VERSION}_arm64.snap --release stable
|
||||
snapcraft push statping_${VERSION}_i386.snap --release stable
|
||||
snapcraft push statping_${VERSION}_armhf.snap --release stable
|
||||
|
||||
sign-all:
|
||||
gpg --default-key $SIGN_KEY --detach-sign --armor statpinger
|
||||
|
||||
|
@ -363,13 +253,6 @@ xgo-install: clean
|
|||
go get github.com/crazy-max/xgo
|
||||
docker pull crazy-max/xgo:${GOVERSION}
|
||||
|
||||
heroku:
|
||||
git push heroku master
|
||||
heroku container:push web
|
||||
heroku container:release web
|
||||
|
||||
checkall:
|
||||
golangci-lint run ./...
|
||||
|
||||
.PHONY: all build build-all build-alpine test-all test test-api docker
|
||||
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite
|
||||
.SILENT: travis_s3_creds
|
||||
|
|
36
README.md
36
README.md
|
@ -3,21 +3,19 @@
|
|||
</p>
|
||||
<p align="center">
|
||||
<b>Statping - Web and App Status Monitoring for Any Type of Project</b><br>
|
||||
<a href="https://github.com/hunterlong/statping/wiki">View Wiki</a> | <a href="https://demo.statping.com">Demo</a> | <a href="https://itunes.apple.com/us/app/apple-store/id1445513219">iPhone</a> | <a href="https://play.google.com/store/apps/details?id=com.statping">Android</a> <br> <a href="https://github.com/hunterlong/statping/wiki/API">API</a> | <a href="https://github.com/hunterlong/statping/wiki/Docker">Docker</a> | <a href="https://github.com/hunterlong/statping/wiki/AWS-EC2">EC2</a> | <a href="https://github.com/hunterlong/statping/wiki/Mac">Mac</a> | <a href="https://github.com/hunterlong/statping/wiki/Linux">Linux</a> | <a href="https://github.com/hunterlong/statping/wiki/Windows">Windows</a> | <a href="https://github.com/hunterlong/statping/wiki/Statping-Plugins">Plugins</a>
|
||||
<a href="https://github.com/statping/statping/wiki">View Wiki</a> | <a href="https://demo.statping.com">Demo</a> | <a href="https://itunes.apple.com/us/app/apple-store/id1445513219">iPhone</a> | <a href="https://play.google.com/store/apps/details?id=com.statping">Android</a> <br> <a href="https://github.com/statping/statping/wiki/API">API</a> | <a href="https://github.com/statping/statping/wiki/Docker">Docker</a> | <a href="https://github.com/statping/statping/wiki/AWS-EC2">EC2</a> | <a href="https://github.com/statping/statping/wiki/Mac">Mac</a> | <a href="https://github.com/statping/statping/wiki/Linux">Linux</a> | <a href="https://github.com/statping/statping/wiki/Windows">Windows</a> | <a href="https://github.com/statping/statping/wiki/Statping-Plugins">Plugins</a>
|
||||
</p>
|
||||
|
||||
# Statping - Status Page & Monitoring Server
|
||||
An easy to use Status Page for your websites and applications. Statping will automatically fetch the application and render a beautiful status page with tons of features for you to build an even better status page. This Status Page generator allows you to use MySQL, Postgres, or SQLite on multiple operating systems.
|
||||
|
||||
[](https://godoc.org/github.com/hunterlong/statping) [](https://gitter.im/statping/general) [](https://microbadger.com/images/hunterlong/statping) [](https://hub.docker.com/r/hunterlong/statping/builds/)
|
||||
[](https://godoc.org/github.com/statping/statping) [](https://gitter.im/statping/general) [](https://microbadger.com/images/hunterlong/statping) [](https://hub.docker.com/r/hunterlong/statping/builds/)
|
||||
|
||||
## A Future-Proof Status Page
|
||||
<img align="left" width="320" height="235" src="https://img.cjx.io/statupsiterun.gif">
|
||||
|
||||
<h2>A Future-Proof Status Page</h2>
|
||||
Statping strives to remain future-proof and remain intact if a failure is created. Your Statping service should not be running on the same instance you're trying to monitor. If your server crashes your Status Page should still remaining online to notify your users of downtime.
|
||||
|
||||
<p align="center">
|
||||
<img width="80%" src="https://img.cjx.io/statupsiterun.gif">
|
||||
</p>
|
||||
|
||||
## Lightweight and Fast
|
||||
Statping is a very lightweight application and is available for Linux, Mac, and Windows. The Docker image is only ~16Mb so you know that this application won't be filling up your hard drive space.
|
||||
The Status binary for all other OS's is ~17Mb at most.
|
||||
|
@ -26,9 +24,9 @@ The Status binary for all other OS's is ~17Mb at most.
|
|||
Statping is built in Go Language so all you need is the precompile binary based on your operating system. You won't need to install anything extra once you have the Statping binary installed. You can even run Statping on a Raspberry Pi.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/hunterlong/statping/wiki/Linux"><img width="5%" src="https://img.cjx.io/linux.png"></a>
|
||||
<a href="https://github.com/hunterlong/statping/wiki/Mac"><img width="5%" src="https://img.cjx.io/apple.png"></a>
|
||||
<a href="https://github.com/hunterlong/statping/wiki/Windows"><img width="5%" src="https://img.cjx.io/windows.png"></a>
|
||||
<a href="https://github.com/statping/statping/wiki/Linux"><img width="5%" src="https://img.cjx.io/linux.png"></a>
|
||||
<a href="https://github.com/statping/statping/wiki/Mac"><img width="5%" src="https://img.cjx.io/apple.png"></a>
|
||||
<a href="https://github.com/statping/statping/wiki/Windows"><img width="5%" src="https://img.cjx.io/windows.png"></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.statping"><img width="5%" src="https://img.cjx.io/android.png"></a>
|
||||
<a href="https://itunes.apple.com/us/app/apple-store/id1445513219"><img width="5%" src="https://img.cjx.io/appstore.png"></a>
|
||||
<a href="https://hub.docker.com/r/hunterlong/statping"><img width="5%" src="https://img.cjx.io/dockericon.png"></a>
|
||||
|
@ -48,7 +46,7 @@ The Statping app is available on the App Store and Google Play for free. The app
|
|||
</p>
|
||||
|
||||
## Run on Any Server
|
||||
Whether you're a Docker fan-boy or a [AWS EC2](https://github.com/hunterlong/statping/wiki/AWS-EC2) master, Statping gives you multiple options to simply get running. Our Amazon AMI image is only 8Gb and will automatically update to the most stable version of Statping.
|
||||
Whether you're a Docker fan-boy or a [AWS EC2](https://github.com/statping/statping/wiki/AWS-EC2) master, Statping gives you multiple options to simply get running. Our Amazon AMI image is only 8Gb and will automatically update to the most stable version of Statping.
|
||||
Running on an EC2 server might be the most cost effective way to host your own Statping Status Page. The server runs on the smallest EC2 instance (t2.nano) AWS has to offer, which only costs around $4.60 USD a month for your dedicated Status Page.
|
||||
Want to run it on your own Docker server? Awesome! Statping has multiple docker-compose.yml files to work with. Statping can automatically create a SSL Certification for your status page.
|
||||
|
||||
|
@ -63,8 +61,8 @@ Statping will allow you to completely customize your Status Page using SASS styl
|
|||
Statping includes email notification via SMTP and Slack integration using [Incoming Webhook](https://api.slack.com/incoming-webhooks). Insert the webhook URL into the Settings page in Statping and enable the Slack integration. Anytime a service fails, you're channel that you specified on Slack will receive a message.
|
||||
|
||||
## User Created Plugins and Notifiers
|
||||
View the [Plugin Wiki](https://github.com/hunterlong/statping/wiki/Statping-Plugins) to see detailed information about Golang Plugins. Statping isn't just another Status Page for your applications, it's a framework that allows you to create your own plugins to interact with every element of your status page. [Notifier's](https://github.com/hunterlong/statping/wiki/Notifiers) can also be create with only 1 golang file.
|
||||
Plugin are created in Golang using the [statping/plugin](https://github.com/hunterlong/statping/tree/master/plugin) golang package. The plugin package has a list of interfaces/events to accept into your own plugin application.
|
||||
View the [Plugin Wiki](https://github.com/statping/statping/wiki/Statping-Plugins) to see detailed information about Golang Plugins. Statping isn't just another Status Page for your applications, it's a framework that allows you to create your own plugins to interact with every element of your status page. [Notifier's](https://github.com/statping/statping/wiki/Notifiers) can also be create with only 1 golang file.
|
||||
Plugin are created in Golang using the [statping/plugin](https://github.com/statping/statping/tree/master/plugin) golang package. The plugin package has a list of interfaces/events to accept into your own plugin application.
|
||||
|
||||
<p align="center">
|
||||
<img width="100%" src="https://img.cjx.io/statupsc2.png">
|
||||
|
@ -85,7 +83,7 @@ statping export
|
|||
###### `index.html` will be created in the current directory with CDN URL's for assets.
|
||||
|
||||
## Run on Docker
|
||||
Use the [Statping Docker Image](https://hub.docker.com/r/hunterlong/statping) to create a status page in seconds. Checkout the [Docker Wiki](https://github.com/hunterlong/statping/wiki/Docker) to view more details on how to get started using Docker.
|
||||
Use the [Statping Docker Image](https://hub.docker.com/r/hunterlong/statping) to create a status page in seconds. Checkout the [Docker Wiki](https://github.com/statping/statping/wiki/Docker) to view more details on how to get started using Docker.
|
||||
```bash
|
||||
docker run -it -p 8080:8080 hunterlong/statping
|
||||
```
|
||||
|
@ -107,7 +105,7 @@ LETSENCRYPT_HOST=mydomain.com \
|
|||
Once your instance has started, it will take a moment to get your SSL certificate. Make sure you have a A or CNAME record on your domain that points to the IP/DNS of your server running Statping.
|
||||
|
||||
## Prometheus Exporter
|
||||
Statping includes a [Prometheus Exporter](https://github.com/hunterlong/statping/wiki/Prometheus-Exporter) so you can have even more monitoring power with your services. The Prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.
|
||||
Statping includes a [Prometheus Exporter](https://github.com/statping/statping/wiki/Prometheus-Exporter) so you can have even more monitoring power with your services. The Prometheus exporter can be seen on `/metrics`, simply create another exporter in your prometheus config. Use your Statping API Secret for the Authorization Bearer header, the `/metrics` URL is dedicated for Prometheus and requires the correct API Secret has `Authorization` header.
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'statping'
|
||||
|
@ -117,7 +115,7 @@ scrape_configs:
|
|||
```
|
||||
|
||||
## Run on EC2 Server
|
||||
Running Statping on the smallest EC2 server is very quick using the AWS AMI Image. Checkout the [AWS Wiki](https://github.com/hunterlong/statping/wiki/AWS-EC2) to see a step by step guide on how to get your EC2 Statping service online.
|
||||
Running Statping on the smallest EC2 server is very quick using the AWS AMI Image. Checkout the [AWS Wiki](https://github.com/statping/statping/wiki/AWS-EC2) to see a step by step guide on how to get your EC2 Statping service online.
|
||||
|
||||
##### Create Security Groups
|
||||
Create the AWS Security Groups with the commands below, Statping will expose port 80 and 443.
|
||||
|
@ -158,11 +156,11 @@ aws ec2 run-instances \
|
|||
```
|
||||
|
||||
## Contributing
|
||||
Statping accepts Push Requests! Feel free to add your own features and notifiers. You probably want to checkout the [Notifier Wiki](https://github.com/hunterlong/statping/wiki/Notifiers) to get a better understanding on how to create your own notification methods for failing/successful services. Testing on Statping will test each function on MySQL, Postgres, and SQLite. I recommend you run a MySQL and a Postgres Docker image for testing.
|
||||
Statping accepts Push Requests! Feel free to add your own features and notifiers. You probably want to checkout the [Notifier Wiki](https://github.com/statping/statping/wiki/Notifiers) to get a better understanding on how to create your own notification methods for failing/successful services. Testing on Statping will test each function on MySQL, Postgres, and SQLite. I recommend you run a MySQL and a Postgres Docker image for testing.
|
||||
|
||||
[](https://goreportcard.com/report/github.com/hunterlong/statping)
|
||||
[](https://goreportcard.com/report/github.com/statping/statping)
|
||||
[](https://travis-ci.com/hunterlong/statping) [](https://dashboard.cypress.io/#/projects/bi8mhr/runs)
|
||||
[](https://hub.docker.com/r/hunterlong/statping/builds/) [](https://godoc.org/github.com/hunterlong/statping)[](https://coveralls.io/github/hunterlong/statping?branch=master)
|
||||
[](https://hub.docker.com/r/hunterlong/statping/builds/) [](https://godoc.org/github.com/statping/statping)[](https://coveralls.io/github/hunterlong/statping?branch=master)
|
||||
|
||||
|
||||
<p align="center">
|
||||
|
|
4
app.json
4
app.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Statping",
|
||||
"description": "Statping Server Monitoring with Status Page",
|
||||
"repository": "https://github.com/hunterlong/statping",
|
||||
"repository": "https://github.com/statping/statping",
|
||||
"logo": "https://raw.githubusercontent.com/hunterlong/statping/master/source/tmpl/banner.png",
|
||||
"keywords": ["statping", "server", "monitoring", "status page","golang", "go"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/statping/statping/source"
|
||||
"github.com/statping/statping/utils"
|
||||
"net"
|
||||
)
|
||||
|
||||
// UsingAssets will return true if /assets folder is present
|
||||
func UsingAssets() bool {
|
||||
return source.UsingAssets(utils.Directory)
|
||||
}
|
||||
|
||||
// GetLocalIP returns the non loopback local IP of the host
|
||||
func GetLocalIP() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return "http://localhost"
|
||||
}
|
||||
for _, address := range addrs {
|
||||
// check the address type and if it is not a loopback the display it
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return fmt.Sprintf("http://%v", ipnet.IP.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return "http://localhost"
|
||||
}
|
230
cmd/cli.go
230
cmd/cli.go
|
@ -2,7 +2,7 @@
|
|||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
// https://github.com/statping/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
|
@ -16,18 +16,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/handlers"
|
||||
"github.com/hunterlong/statping/plugin"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/statping/statping/handlers"
|
||||
"github.com/statping/statping/source"
|
||||
"github.com/statping/statping/types/configs"
|
||||
"github.com/statping/statping/types/core"
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/utils"
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -36,7 +38,6 @@ func catchCLI(args []string) error {
|
|||
dir := utils.Directory
|
||||
runLogs := utils.InitLogs
|
||||
runAssets := source.Assets
|
||||
loadDotEnvs()
|
||||
|
||||
switch args[0] {
|
||||
case "version":
|
||||
|
@ -65,40 +66,33 @@ func catchCLI(args []string) error {
|
|||
if err := runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := source.CompileSASS(dir); err != nil {
|
||||
if err := source.CompileSASS(source.DefaultScss...); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("end")
|
||||
case "update":
|
||||
updateDisplay()
|
||||
return errors.New("end")
|
||||
case "test":
|
||||
cmd := args[1]
|
||||
switch cmd {
|
||||
case "plugins":
|
||||
plugin.LoadPlugins()
|
||||
}
|
||||
return errors.New("end")
|
||||
case "static":
|
||||
var err error
|
||||
if err = runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION)
|
||||
if core.CoreApp.Config, err = core.LoadConfigFile(dir); err != nil {
|
||||
log.Errorln("config.yml file not found")
|
||||
return err
|
||||
}
|
||||
indexSource := ExportIndexHTML()
|
||||
//core.CloseDB()
|
||||
if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
log.Infoln("Exported Statping index page: 'index.html'")
|
||||
//var err error
|
||||
//if err = runLogs(); err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if err = runAssets(); err != nil {
|
||||
// return err
|
||||
//}
|
||||
//fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION)
|
||||
//if _, err = core.LoadConfigFile(dir); err != nil {
|
||||
// log.Errorln("config.yml file not found")
|
||||
// return err
|
||||
//}
|
||||
//indexSource := ExportIndexHTML()
|
||||
////core.CloseDB()
|
||||
//if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
|
||||
// log.Errorln(err)
|
||||
// return err
|
||||
//}
|
||||
//log.Infoln("Exported Statping index page: 'index.html'")
|
||||
case "help":
|
||||
HelpEcho()
|
||||
return errors.New("end")
|
||||
|
@ -111,19 +105,24 @@ func catchCLI(args []string) error {
|
|||
if err = runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
if core.CoreApp.Config, err = core.LoadConfigFile(dir); err != nil {
|
||||
config, err := configs.LoadConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = core.CoreApp.Connect(false, dir); err != nil {
|
||||
if err = configs.ConnectConfigs(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if data, err = core.ExportSettings(); err != nil {
|
||||
if _, err := services.SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if data, err = handlers.ExportSettings(); err != nil {
|
||||
return fmt.Errorf("could not export settings: %v", err.Error())
|
||||
}
|
||||
//core.CloseDB()
|
||||
if err = utils.SaveFile(dir+"/statping-export.json", data); err != nil {
|
||||
filename := fmt.Sprintf("%s/statping-%s.json", dir, time.Now().Format("01-02-2006-1504"))
|
||||
if err = utils.SaveFile(filename, data); err != nil {
|
||||
return fmt.Errorf("could not write file statping-export.json: %v", err.Error())
|
||||
}
|
||||
log.Infoln("Statping export file saved to ", filename)
|
||||
return errors.New("end")
|
||||
case "import":
|
||||
var err error
|
||||
|
@ -135,10 +134,75 @@ func catchCLI(args []string) error {
|
|||
if data, err = ioutil.ReadFile(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
var exportData core.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))
|
||||
|
||||
config, err := configs.LoadConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = configs.ConnectConfigs(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if data, err = handlers.ExportSettings(); err != nil {
|
||||
return fmt.Errorf("could not export settings: %v", err.Error())
|
||||
}
|
||||
|
||||
if ask("Import Core settings?") {
|
||||
c := exportData.Core
|
||||
if err := c.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, s := range exportData.Groups {
|
||||
if ask(fmt.Sprintf("Import Group '%s'?", s.Name)) {
|
||||
s.Id = 0
|
||||
if err := s.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range exportData.Services {
|
||||
if ask(fmt.Sprintf("Import Service '%s'?", s.Name)) {
|
||||
s.Id = 0
|
||||
if err := s.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range exportData.Checkins {
|
||||
if ask(fmt.Sprintf("Import Checkin '%s'?", s.Name)) {
|
||||
s.Id = 0
|
||||
if err := s.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range exportData.Messages {
|
||||
if ask(fmt.Sprintf("Import Message '%s'?", s.Title)) {
|
||||
s.Id = 0
|
||||
if err := s.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range exportData.Users {
|
||||
if ask(fmt.Sprintf("Import User '%s'?", s.Username)) {
|
||||
s.Id = 0
|
||||
if err := s.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Infof("Import complete")
|
||||
return errors.New("end")
|
||||
case "run":
|
||||
if err := runLogs(); err != nil {
|
||||
|
@ -174,31 +238,42 @@ func catchCLI(args []string) error {
|
|||
return errors.New("end")
|
||||
}
|
||||
|
||||
// ExportIndexHTML returns the HTML of the index page as a string
|
||||
func ExportIndexHTML() []byte {
|
||||
source.Assets()
|
||||
core.CoreApp.Connect(false, utils.Directory)
|
||||
core.CoreApp.SelectAllServices(false)
|
||||
core.CoreApp.UseCdn = types.NewNullBool(true)
|
||||
for _, srv := range core.CoreApp.Services {
|
||||
service := srv.(*core.Service)
|
||||
service.Check(true)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil)
|
||||
return w.Body.Bytes()
|
||||
func ask(format string) bool {
|
||||
fmt.Printf(fmt.Sprintf(format + " [y/N]: "))
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
text, _ := reader.ReadString('\n')
|
||||
text = strings.Replace(text, "\n", "", -1)
|
||||
return strings.ToLower(text) == "y"
|
||||
}
|
||||
|
||||
// ExportIndexHTML returns the HTML of the index page as a string
|
||||
//func ExportIndexHTML() []byte {
|
||||
// source.Assets()
|
||||
// core.CoreApp.Connect(core.CoreApp., utils.Directory)
|
||||
// core.SelectAllServices(false)
|
||||
// core.CoreApp.UseCdn = types.NewNullBool(true)
|
||||
// for _, srv := range core.Services() {
|
||||
// core.CheckService(srv, true)
|
||||
// }
|
||||
// w := httptest.NewRecorder()
|
||||
// r := httptest.NewRequest("GET", "/", nil)
|
||||
// handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil)
|
||||
// return w.Body.Bytes()
|
||||
//}
|
||||
|
||||
func updateDisplay() error {
|
||||
var err error
|
||||
var gitCurrent githubResponse
|
||||
if gitCurrent, err = checkGithubUpdates(); err != nil {
|
||||
fmt.Printf("Issue connecting to https://github.com/hunterlong/statping\n%v\n", err)
|
||||
return err
|
||||
gitCurrent, err := checkGithubUpdates()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Issue connecting to https://github.com/statping/statping")
|
||||
}
|
||||
if gitCurrent.TagName == "" {
|
||||
return nil
|
||||
}
|
||||
if len(gitCurrent.TagName) < 2 {
|
||||
return nil
|
||||
}
|
||||
if VERSION != gitCurrent.TagName[1:] {
|
||||
fmt.Printf("\nNew Update %v Available!\n", gitCurrent.TagName[1:])
|
||||
fmt.Printf("New Update %v Available!\n", gitCurrent.TagName[1:])
|
||||
fmt.Printf("Update Command:\n")
|
||||
fmt.Printf("curl -o- -L https://statping.com/install.sh | bash\n\n")
|
||||
}
|
||||
|
@ -206,27 +281,30 @@ func updateDisplay() error {
|
|||
}
|
||||
|
||||
// runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
|
||||
func runOnce() {
|
||||
var err error
|
||||
core.CoreApp.Config, err = core.LoadConfigFile(utils.Directory)
|
||||
func runOnce() error {
|
||||
config, err := configs.LoadConfigs()
|
||||
if err != nil {
|
||||
log.Errorln("config.yml file not found")
|
||||
return errors.Wrap(err, "config.yml file not found")
|
||||
}
|
||||
err = core.CoreApp.Connect(false, utils.Directory)
|
||||
err = configs.ConnectConfigs(config)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return errors.Wrap(err, "issue connecting to database")
|
||||
}
|
||||
core.CoreApp, err = core.SelectCore()
|
||||
c, err := core.Select()
|
||||
if err != nil {
|
||||
fmt.Println("Core database was not found, Statping is not setup yet.")
|
||||
return errors.Wrap(err, "core database was not found or setup")
|
||||
}
|
||||
_, err = core.CoreApp.SelectAllServices(true)
|
||||
|
||||
core.App = c
|
||||
|
||||
_, err = services.SelectAllServices(true)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return errors.Wrap(err, "could not select all services")
|
||||
}
|
||||
for _, out := range core.CoreApp.Services {
|
||||
out.Check(true)
|
||||
for _, srv := range services.Services() {
|
||||
srv.CheckService(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HelpEcho prints out available commands and flags for Statping
|
||||
|
@ -276,7 +354,7 @@ func HelpEcho() {
|
|||
fmt.Println(" AUTH_PASSWORD - HTTP Basic Authentication password")
|
||||
fmt.Println(" BASE_PATH - Set the base URL prefix (set to 'monitor' if URL is domain.com/monitor)")
|
||||
fmt.Println(" * You can insert environment variables into a '.env' file in root directory.")
|
||||
fmt.Println("Give Statping a Star at https://github.com/hunterlong/statping")
|
||||
fmt.Println("Give Statping a Star at https://github.com/statping/statping")
|
||||
}
|
||||
|
||||
func checkGithubUpdates() (githubResponse, error) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
// https://github.com/statping/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
|
@ -16,10 +16,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"fmt"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/rendon/testcli"
|
||||
"github.com/statping/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
@ -32,7 +34,15 @@ var (
|
|||
|
||||
func init() {
|
||||
dir = utils.Directory
|
||||
core.SampleHits = 480
|
||||
//core.SampleHits = 480
|
||||
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: errorReporter,
|
||||
Environment: "testing",
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStartServerCommand(t *testing.T) {
|
||||
|
@ -84,6 +94,7 @@ func TestExportCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateCommand(t *testing.T) {
|
||||
t.SkipNow()
|
||||
cmd := helperCommand(nil, "version")
|
||||
var got = make(chan string)
|
||||
commandAndSleep(cmd, time.Duration(15*time.Second), got)
|
||||
|
@ -93,13 +104,23 @@ func TestUpdateCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAssetsCommand(t *testing.T) {
|
||||
t.SkipNow()
|
||||
c := testcli.Command("statping", "assets")
|
||||
c.Run()
|
||||
t.Log(c.Stdout())
|
||||
t.Log("Directory for Assets: ", dir)
|
||||
time.Sleep(1 * time.Second)
|
||||
err := utils.DeleteDirectory(dir + "/assets")
|
||||
require.Nil(t, err)
|
||||
assert.FileExists(t, dir+"/assets/robots.txt")
|
||||
assert.FileExists(t, dir+"/assets/scss/base.scss")
|
||||
assert.FileExists(t, dir+"/assets/scss/main.scss")
|
||||
assert.FileExists(t, dir+"/assets/scss/variables.scss")
|
||||
assert.FileExists(t, dir+"/assets/css/main.css")
|
||||
assert.FileExists(t, dir+"/assets/css/vendor.css")
|
||||
assert.FileExists(t, dir+"/assets/css/style.css")
|
||||
err = utils.DeleteDirectory(dir + "/assets")
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestRunCommand(t *testing.T) {
|
||||
|
@ -127,13 +148,19 @@ func TestVersionCLI(t *testing.T) {
|
|||
func TestAssetsCLI(t *testing.T) {
|
||||
catchCLI([]string{"assets"})
|
||||
//assert.EqualError(t, run, "end")
|
||||
assert.FileExists(t, dir+"/assets/css/base.css")
|
||||
assert.FileExists(t, dir+"/assets/css/main.css")
|
||||
assert.FileExists(t, dir+"/assets/css/style.css")
|
||||
assert.FileExists(t, dir+"/assets/css/vendor.css")
|
||||
assert.FileExists(t, dir+"/assets/scss/base.scss")
|
||||
assert.FileExists(t, dir+"/assets/scss/mobile.scss")
|
||||
assert.FileExists(t, dir+"/assets/scss/variables.scss")
|
||||
}
|
||||
|
||||
func TestSassCLI(t *testing.T) {
|
||||
catchCLI([]string{"sass"})
|
||||
assert.FileExists(t, dir+"/assets/css/base.css")
|
||||
assert.FileExists(t, dir+"/assets/css/main.css")
|
||||
assert.FileExists(t, dir+"/assets/css/style.css")
|
||||
assert.FileExists(t, dir+"/assets/css/vendor.css")
|
||||
}
|
||||
|
||||
func TestUpdateCLI(t *testing.T) {
|
||||
|
@ -146,12 +173,6 @@ func TestUpdateCLI(t *testing.T) {
|
|||
assert.Contains(t, gg, "version")
|
||||
}
|
||||
|
||||
func TestTestPackageCLI(t *testing.T) {
|
||||
t.SkipNow()
|
||||
run := catchCLI([]string{"test", "plugins"})
|
||||
assert.EqualError(t, run, "end")
|
||||
}
|
||||
|
||||
func TestHelpCLI(t *testing.T) {
|
||||
run := catchCLI([]string{"help"})
|
||||
assert.EqualError(t, run, "end")
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/statping/statping/types/checkins"
|
||||
"github.com/statping/statping/types/failures"
|
||||
"github.com/statping/statping/types/groups"
|
||||
"github.com/statping/statping/types/hits"
|
||||
"github.com/statping/statping/types/incidents"
|
||||
"github.com/statping/statping/types/messages"
|
||||
"github.com/statping/statping/types/notifications"
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/types/users"
|
||||
)
|
||||
|
||||
var (
|
||||
// DbSession stores the Statping database session
|
||||
DbModels []interface{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}}
|
||||
}
|
|
@ -23,5 +23,5 @@
|
|||
// docker pull karalabe/xgo-latest
|
||||
// build-all
|
||||
//
|
||||
// More info on: https://github.com/hunterlong/statping
|
||||
// More info on: https://github.com/statping/statping
|
||||
package main
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/statping/statping/database"
|
||||
"github.com/statping/statping/notifiers"
|
||||
"github.com/statping/statping/types/core"
|
||||
"github.com/statping/statping/types/services"
|
||||
)
|
||||
|
||||
func InitApp() error {
|
||||
if _, err := core.Select(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := services.SelectAllServices(true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go services.CheckServices()
|
||||
|
||||
notifiers.InitNotifiers()
|
||||
|
||||
database.StartMaintenceRoutine()
|
||||
core.App.Setup = true
|
||||
return nil
|
||||
}
|
212
cmd/main.go
212
cmd/main.go
|
@ -2,7 +2,7 @@
|
|||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
// https://github.com/statping/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
|
@ -16,18 +16,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/utils"
|
||||
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/handlers"
|
||||
"github.com/hunterlong/statping/plugin"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/statping/statping/source"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/statping/statping/handlers"
|
||||
"github.com/statping/statping/types/configs"
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -40,44 +43,55 @@ var (
|
|||
verboseMode int
|
||||
port int
|
||||
log = utils.Log.WithField("type", "cmd")
|
||||
httpServer = make(chan bool)
|
||||
|
||||
confgs *configs.DbConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
core.VERSION = VERSION
|
||||
|
||||
}
|
||||
|
||||
// parseFlags will parse the application flags
|
||||
// -ip = 0.0.0.0 IP address for outgoing HTTP server
|
||||
// -port = 8080 Port number for outgoing HTTP server
|
||||
// environment variables WILL overwrite flags
|
||||
func parseFlags() {
|
||||
flag.StringVar(&ipAddress, "ip", "0.0.0.0", "IP address to run the Statping HTTP server")
|
||||
flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server")
|
||||
flag.IntVar(&port, "port", 8080, "Port to run the HTTP server")
|
||||
flag.IntVar(&verboseMode, "verbose", 2, "Run in verbose mode to see detailed logs (1 - 4)")
|
||||
flag.Parse()
|
||||
envPort := utils.Getenv("PORT", 8080).(int)
|
||||
envIpAddress := utils.Getenv("IP", "0.0.0.0").(string)
|
||||
envVerbose := utils.Getenv("VERBOSE", 2).(int)
|
||||
|
||||
if os.Getenv("PORT") != "" {
|
||||
port = int(utils.ToInt(os.Getenv("PORT")))
|
||||
}
|
||||
if os.Getenv("IP") != "" {
|
||||
ipAddress = os.Getenv("IP")
|
||||
}
|
||||
if os.Getenv("VERBOSE") != "" {
|
||||
verboseMode = int(utils.ToInt(os.Getenv("VERBOSE")))
|
||||
}
|
||||
flag.StringVar(&ipAddress, "ip", envIpAddress, "IP address to run the Statping HTTP server")
|
||||
flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server")
|
||||
flag.IntVar(&port, "port", envPort, "Port to run the HTTP server")
|
||||
flag.IntVar(&verboseMode, "verbose", envVerbose, "Run in verbose mode to see detailed logs (1 - 4)")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func exit(err error) {
|
||||
sentry.CaptureException(err)
|
||||
log.Fatalln(err)
|
||||
Close()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// main will run the Statping application
|
||||
func main() {
|
||||
var err error
|
||||
go sigterm()
|
||||
|
||||
parseFlags()
|
||||
loadDotEnvs()
|
||||
source.Assets()
|
||||
|
||||
if err := source.Assets(); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
utils.VerboseMode = verboseMode
|
||||
|
||||
if err := utils.InitLogs(); err != nil {
|
||||
log.Errorf("Statping Log Error: %v\n", err)
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) >= 1 {
|
||||
|
@ -85,75 +99,143 @@ func main() {
|
|||
if err != nil {
|
||||
if err.Error() == "end" {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
exit(err)
|
||||
}
|
||||
}
|
||||
log.Info(fmt.Sprintf("Starting Statping v%v", VERSION))
|
||||
updateDisplay()
|
||||
|
||||
configs, err := core.LoadConfigFile(utils.Directory)
|
||||
if err != nil {
|
||||
if err := updateDisplay(); err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
|
||||
errorEnv := utils.Getenv("GO_ENV", "production").(string)
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: errorReporter,
|
||||
Environment: errorEnv,
|
||||
}); err != nil {
|
||||
log.Errorln(err)
|
||||
core.SetupMode = true
|
||||
writeAble, err := utils.DirWritable(utils.Directory)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if !writeAble {
|
||||
log.Fatalf("Statping does not have write permissions at: %v\nYou can change this directory by setting the STATPING_DIR environment variable to a dedicated path before starting.", utils.Directory)
|
||||
}
|
||||
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
confgs, err = configs.LoadConfigs()
|
||||
if err != nil {
|
||||
if err := SetupMode(); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
}
|
||||
core.CoreApp.Config = configs
|
||||
|
||||
if err = configs.ConnectConfigs(confgs); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
exists := confgs.Db.HasTable("core")
|
||||
if !exists {
|
||||
var srvs int64
|
||||
confgs.Db.Model(&services.Service{}).Count(&srvs)
|
||||
if srvs > 0 {
|
||||
exit(errors.Wrap(err, "there are already services setup."))
|
||||
}
|
||||
|
||||
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 err := configs.TriggerSamples(); err != nil {
|
||||
exit(errors.Wrap(err, "error creating database"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err = confgs.DatabaseChanges(); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
if err := confgs.MigrateDatabase(); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
//log.Infoln("Migrating Notifiers...")
|
||||
//if err := notifier.Migrate(); err != nil {
|
||||
// exit(errors.Wrap(err, "error migrating notifiers"))
|
||||
//}
|
||||
//log.Infoln("Notifiers Migrated")
|
||||
|
||||
if err := mainProcess(); err != nil {
|
||||
log.Fatalln(err)
|
||||
exit(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Close will gracefully stop the database connection, and log file
|
||||
func Close() {
|
||||
core.CloseDB()
|
||||
sentry.Flush(3 * time.Second)
|
||||
utils.CloseLogs()
|
||||
confgs.Close()
|
||||
}
|
||||
|
||||
func SetupMode() error {
|
||||
return handlers.RunHTTPServer(ipAddress, port)
|
||||
}
|
||||
|
||||
// sigterm will attempt to close the database connections gracefully
|
||||
func sigterm() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
fmt.Println("Shutting down Statping")
|
||||
Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// loadDotEnvs attempts to load database configs from a '.env' file in root directory
|
||||
func loadDotEnvs() error {
|
||||
err := godotenv.Load(envFile)
|
||||
if err == nil {
|
||||
log.Infoln("Environment file '.env' Loaded")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// mainProcess will initialize the Statping application and run the HTTP server
|
||||
func mainProcess() error {
|
||||
dir := utils.Directory
|
||||
var err error
|
||||
err = core.CoreApp.Connect(false, dir)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Sprintf("could not connect to database: %v", err))
|
||||
if err := services.ServicesFromEnvFile(); err != nil {
|
||||
errStr := "error 'SERVICE' environment variable"
|
||||
log.Errorln(errStr)
|
||||
return errors.Wrap(err, errStr)
|
||||
}
|
||||
|
||||
if err := InitApp(); err != nil {
|
||||
return err
|
||||
}
|
||||
core.CoreApp.MigrateDatabase()
|
||||
core.InitApp()
|
||||
if !core.SetupMode {
|
||||
plugin.LoadPlugins()
|
||||
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
|
||||
log.Fatalln(err)
|
||||
|
||||
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
|
||||
log.Fatalln(err)
|
||||
return errors.Wrap(err, "http server")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartHTTPServer() {
|
||||
httpServer = make(chan bool)
|
||||
go httpServerProcess(httpServer)
|
||||
}
|
||||
|
||||
func StopHTTPServer() {
|
||||
|
||||
}
|
||||
|
||||
func httpServerProcess(process <-chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-process:
|
||||
fmt.Println("HTTP Server has stopped")
|
||||
return
|
||||
default:
|
||||
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
|
||||
log.Errorln(err)
|
||||
exit(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
const errorReporter = "https://2bedd272821643e1b92c774d3fdf28e7@sentry.statping.com/2"
|
||||
|
|
267
core/checkin.go
267
core/checkin.go
|
@ -1,267 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Checkin struct {
|
||||
*types.Checkin
|
||||
}
|
||||
|
||||
type CheckinHit struct {
|
||||
*types.CheckinHit
|
||||
}
|
||||
|
||||
// Select returns a *types.Checkin
|
||||
func (c *Checkin) Select() *types.Checkin {
|
||||
return c.Checkin
|
||||
}
|
||||
|
||||
// Routine for checking if the last Checkin was within its interval
|
||||
func (c *Checkin) Routine() {
|
||||
if c.Last() == nil {
|
||||
return
|
||||
}
|
||||
reCheck := c.Period()
|
||||
CheckinLoop:
|
||||
for {
|
||||
select {
|
||||
case <-c.Running:
|
||||
log.Infoln(fmt.Sprintf("Stopping checkin routine: %v", c.Name))
|
||||
c.Failing = false
|
||||
break CheckinLoop
|
||||
case <-time.After(reCheck):
|
||||
log.Infoln(fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
|
||||
if c.Expected().Seconds() <= 0 {
|
||||
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt)
|
||||
log.Errorln(issue)
|
||||
c.CreateFailure()
|
||||
}
|
||||
reCheck = c.Period()
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// String will return a Checkin API string
|
||||
func (c *Checkin) String() string {
|
||||
return c.ApiKey
|
||||
}
|
||||
|
||||
// ReturnCheckin converts *types.Checking to *core.Checkin
|
||||
func ReturnCheckin(c *types.Checkin) *Checkin {
|
||||
return &Checkin{Checkin: c}
|
||||
}
|
||||
|
||||
// ReturnCheckinHit converts *types.checkinHit to *core.checkinHit
|
||||
func ReturnCheckinHit(c *types.CheckinHit) *CheckinHit {
|
||||
return &CheckinHit{CheckinHit: c}
|
||||
}
|
||||
|
||||
func (c *Checkin) Service() *Service {
|
||||
return SelectService(c.ServiceId)
|
||||
}
|
||||
|
||||
func (c *Checkin) CreateFailure() (int64, error) {
|
||||
service := c.Service()
|
||||
c.Failing = true
|
||||
fail := &Failure{&types.Failure{
|
||||
Issue: fmt.Sprintf("Checkin %v was not reported %v ago, it expects a request every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())),
|
||||
Method: "checkin",
|
||||
MethodId: c.Id,
|
||||
Service: service.Id,
|
||||
Checkin: c.Id,
|
||||
PingTime: c.Expected().Seconds(),
|
||||
}}
|
||||
row := failuresDB().Create(&fail)
|
||||
sort.Sort(types.FailSort(c.Failures))
|
||||
c.Failures = append(c.Failures, fail)
|
||||
if len(c.Failures) > limitedFailures {
|
||||
c.Failures = c.Failures[1:]
|
||||
}
|
||||
return fail.Id, row.Error
|
||||
}
|
||||
|
||||
// LimitedHits will return the last amount of successful hits from a checkin
|
||||
func (c *Checkin) LimitedHits(amount int64) []*types.CheckinHit {
|
||||
var hits []*types.CheckinHit
|
||||
checkinHitsDB().Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
|
||||
return hits
|
||||
}
|
||||
|
||||
// AllCheckins returns all checkin in system
|
||||
func AllCheckins() []*Checkin {
|
||||
var checkins []*Checkin
|
||||
checkinDB().Find(&checkins)
|
||||
return checkins
|
||||
}
|
||||
|
||||
// SelectCheckin will find a Checkin based on the API supplied
|
||||
func SelectCheckin(api string) *Checkin {
|
||||
for _, s := range Services() {
|
||||
for _, c := range s.Select().Checkins {
|
||||
if c.Select().ApiKey == api {
|
||||
return c.(*Checkin)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Period will return the duration of the Checkin interval
|
||||
func (c *Checkin) Period() time.Duration {
|
||||
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.Interval))
|
||||
return duration
|
||||
}
|
||||
|
||||
// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response)
|
||||
func (c *Checkin) Grace() time.Duration {
|
||||
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod))
|
||||
return duration
|
||||
}
|
||||
|
||||
// Expected returns the duration of when the serviec should receive a Checkin
|
||||
func (c *Checkin) Expected() time.Duration {
|
||||
last := c.Last().CreatedAt
|
||||
now := utils.Now()
|
||||
lastDir := now.Sub(last)
|
||||
sub := time.Duration(c.Period() - lastDir)
|
||||
return sub
|
||||
}
|
||||
|
||||
// Last returns the last checkinHit for a Checkin
|
||||
func (c *Checkin) Last() *CheckinHit {
|
||||
var hit CheckinHit
|
||||
checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit)
|
||||
return &hit
|
||||
}
|
||||
|
||||
func (c *Checkin) Link() string {
|
||||
return fmt.Sprintf("%v/checkin/%v", CoreApp.Domain, c.ApiKey)
|
||||
}
|
||||
|
||||
// AllHits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) AllHits() []*types.CheckinHit {
|
||||
var checkins []*types.CheckinHit
|
||||
checkinHitsDB().Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins)
|
||||
return checkins
|
||||
}
|
||||
|
||||
// Hits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) LimitedFailures(amount int64) []types.FailureInterface {
|
||||
var failures []*Failure
|
||||
var failInterfaces []types.FailureInterface
|
||||
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
|
||||
col.Find(&failures)
|
||||
for _, f := range failures {
|
||||
failInterfaces = append(failInterfaces, f)
|
||||
}
|
||||
return failInterfaces
|
||||
}
|
||||
|
||||
// Hits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) AllFailures() []*types.Failure {
|
||||
var failures []*types.Failure
|
||||
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Order("id desc")
|
||||
col.Find(&failures)
|
||||
return failures
|
||||
}
|
||||
|
||||
// Create will create a new Checkin
|
||||
func (c *Checkin) Delete() error {
|
||||
c.Close()
|
||||
i := c.index()
|
||||
service := c.Service()
|
||||
slice := service.Checkins
|
||||
service.Checkins = append(slice[:i], slice[i+1:]...)
|
||||
row := checkinDB().Delete(&c)
|
||||
return row.Error
|
||||
}
|
||||
|
||||
// index returns a checkin index int for updating the *checkin.Service slice
|
||||
func (c *Checkin) index() int {
|
||||
for k, checkin := range c.Service().Checkins {
|
||||
if c.Id == checkin.Select().Id {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Create will create a new Checkin
|
||||
func (c *Checkin) Create() (int64, error) {
|
||||
c.ApiKey = utils.RandomString(7)
|
||||
row := checkinDB().Create(&c)
|
||||
if row.Error != nil {
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
service := SelectService(c.ServiceId)
|
||||
service.Checkins = append(service.Checkins, c)
|
||||
c.Start()
|
||||
go c.Routine()
|
||||
return c.Id, row.Error
|
||||
}
|
||||
|
||||
// Update will update a Checkin
|
||||
func (c *Checkin) Update() (int64, error) {
|
||||
row := checkinDB().Update(&c)
|
||||
if row.Error != nil {
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
return c.Id, row.Error
|
||||
}
|
||||
|
||||
// Create will create a new successful checkinHit
|
||||
func (c *CheckinHit) Create() (int64, error) {
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = utils.Now()
|
||||
}
|
||||
row := checkinHitsDB().Create(&c)
|
||||
if row.Error != nil {
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
return c.Id, row.Error
|
||||
}
|
||||
|
||||
// Ago returns the duration of time between now and the last successful checkinHit
|
||||
func (c *CheckinHit) Ago() string {
|
||||
got, _ := timeago.TimeAgoWithTime(utils.Now(), c.CreatedAt)
|
||||
return got
|
||||
}
|
||||
|
||||
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
|
||||
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
|
||||
between := utils.Now().Sub(utils.Now()).Seconds()
|
||||
if between > float64(c.Interval) {
|
||||
fmt.Println("rechecking every 15 seconds!")
|
||||
time.Sleep(15 * time.Second)
|
||||
guard <- struct{}{}
|
||||
c.RecheckCheckinFailure(guard)
|
||||
} else {
|
||||
fmt.Println("i recovered!!")
|
||||
}
|
||||
<-guard
|
||||
}
|
207
core/configs.go
207
core/configs.go
|
@ -1,207 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ErrorResponse is used for HTTP errors to show to User
|
||||
type ErrorResponse struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
// LoadConfigFile will attempt to load the 'config.yml' file in a specific directory
|
||||
func LoadConfigFile(directory string) (*types.DbConfig, error) {
|
||||
var configs *types.DbConfig
|
||||
if os.Getenv("DB_CONN") != "" {
|
||||
log.Warnln("DB_CONN environment variable was found, waiting for database...")
|
||||
return LoadUsingEnv()
|
||||
}
|
||||
log.Debugln("attempting to read config file at: " + directory + "/config.yml")
|
||||
file, err := ioutil.ReadFile(directory + "/config.yml")
|
||||
if err != nil {
|
||||
return nil, errors.New("config.yml file not found at " + directory + "/config.yml - starting in setup mode")
|
||||
}
|
||||
err = yaml.Unmarshal(file, &configs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + directory + "/config.yml")
|
||||
CoreApp.Config = configs
|
||||
return configs, err
|
||||
}
|
||||
|
||||
// LoadUsingEnv will attempt to load database configs based on environment variables. If DB_CONN is set if will force this function.
|
||||
func LoadUsingEnv() (*types.DbConfig, error) {
|
||||
Configs, err := EnvToConfig()
|
||||
if err != nil {
|
||||
return Configs, err
|
||||
}
|
||||
CoreApp.Name = os.Getenv("NAME")
|
||||
if Configs.Domain == "" {
|
||||
CoreApp.Domain = Configs.LocalIP
|
||||
} else {
|
||||
CoreApp.Domain = os.Getenv("DOMAIN")
|
||||
}
|
||||
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
|
||||
|
||||
err = CoreApp.Connect(true, utils.Directory)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
CoreApp.SaveConfig(Configs)
|
||||
exists := DbSession.HasTable("core")
|
||||
if !exists {
|
||||
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
|
||||
CoreApp.DropDatabase()
|
||||
CoreApp.CreateDatabase()
|
||||
CoreApp, err = CoreApp.InsertCore(Configs)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
username := os.Getenv("ADMIN_USER")
|
||||
if username == "" {
|
||||
username = "admin"
|
||||
}
|
||||
password := os.Getenv("ADMIN_PASSWORD")
|
||||
if password == "" {
|
||||
password = "admin"
|
||||
}
|
||||
|
||||
admin := ReturnUser(&types.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: "info@admin.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
_, err := admin.Create()
|
||||
|
||||
SampleData()
|
||||
return Configs, err
|
||||
}
|
||||
return Configs, nil
|
||||
}
|
||||
|
||||
// defaultPort accepts a database type and returns its default port
|
||||
func defaultPort(db string) int64 {
|
||||
switch db {
|
||||
case "mysql":
|
||||
return 3306
|
||||
case "postgres":
|
||||
return 5432
|
||||
case "mssql":
|
||||
return 1433
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// EnvToConfig converts environment variables to a DbConfig variable
|
||||
func EnvToConfig() (*types.DbConfig, error) {
|
||||
var err error
|
||||
if os.Getenv("DB_CONN") == "" {
|
||||
return nil, errors.New("Missing DB_CONN environment variable")
|
||||
}
|
||||
if os.Getenv("DB_CONN") != "sqlite" {
|
||||
if os.Getenv("DB_HOST") == "" {
|
||||
return nil, errors.New("Missing DB_HOST environment variable")
|
||||
}
|
||||
if os.Getenv("DB_USER") == "" {
|
||||
return nil, errors.New("Missing DB_USER environment variable")
|
||||
}
|
||||
if os.Getenv("DB_PASS") == "" {
|
||||
return nil, errors.New("Missing DB_PASS environment variable")
|
||||
}
|
||||
if os.Getenv("DB_DATABASE") == "" {
|
||||
return nil, errors.New("Missing DB_DATABASE environment variable")
|
||||
}
|
||||
}
|
||||
port := utils.ToInt(os.Getenv("DB_PORT"))
|
||||
if port == 0 {
|
||||
port = defaultPort(os.Getenv("DB_PORT"))
|
||||
}
|
||||
name := os.Getenv("NAME")
|
||||
if name == "" {
|
||||
name = "Statping"
|
||||
}
|
||||
description := os.Getenv("DESCRIPTION")
|
||||
if description == "" {
|
||||
description = "Statping Monitoring Sample Data"
|
||||
}
|
||||
|
||||
adminUser := os.Getenv("ADMIN_USER")
|
||||
if adminUser == "" {
|
||||
adminUser = "admin"
|
||||
}
|
||||
|
||||
adminPass := os.Getenv("ADMIN_PASS")
|
||||
if adminPass == "" {
|
||||
adminPass = "admin"
|
||||
}
|
||||
|
||||
configs := &types.DbConfig{
|
||||
DbConn: os.Getenv("DB_CONN"),
|
||||
DbHost: os.Getenv("DB_HOST"),
|
||||
DbUser: os.Getenv("DB_USER"),
|
||||
DbPass: os.Getenv("DB_PASS"),
|
||||
DbData: os.Getenv("DB_DATABASE"),
|
||||
DbPort: port,
|
||||
Project: name,
|
||||
Description: description,
|
||||
Domain: os.Getenv("DOMAIN"),
|
||||
Email: "",
|
||||
Username: adminUser,
|
||||
Password: adminPass,
|
||||
Error: nil,
|
||||
Location: utils.Directory,
|
||||
SqlFile: os.Getenv("SQL_FILE"),
|
||||
}
|
||||
CoreApp.Config = configs
|
||||
return configs, err
|
||||
}
|
||||
|
||||
// SampleData runs all the sample data for a new Statping installation
|
||||
func SampleData() error {
|
||||
if err := InsertSampleData(); err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
if err := InsertSampleHits(); err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteConfig will delete the 'config.yml' file
|
||||
func DeleteConfig() error {
|
||||
log.Debugln("deleting config yaml file", utils.Directory+"/config.yml")
|
||||
err := utils.DeleteFile(utils.Directory + "/config.yml")
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
208
core/core.go
208
core/core.go
|
@ -1,208 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/integrations"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/notifiers"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PluginJSON types.PluginJSON
|
||||
type PluginRepos types.PluginRepos
|
||||
|
||||
type Core struct {
|
||||
*types.Core
|
||||
}
|
||||
|
||||
var (
|
||||
CoreApp *Core // CoreApp is a global variable that contains many elements
|
||||
SetupMode bool // SetupMode will be true if Statping does not have a database connection
|
||||
VERSION string // VERSION is set on build automatically by setting a -ldflag
|
||||
log = utils.Log.WithField("type", "core")
|
||||
)
|
||||
|
||||
func init() {
|
||||
CoreApp = NewCore()
|
||||
}
|
||||
|
||||
// NewCore return a new *core.Core struct
|
||||
func NewCore() *Core {
|
||||
CoreApp = &Core{&types.Core{
|
||||
Started: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
return CoreApp
|
||||
}
|
||||
|
||||
// ToCore will convert *core.Core to *types.Core
|
||||
func (c *Core) ToCore() *types.Core {
|
||||
return c.Core
|
||||
}
|
||||
|
||||
// InitApp will initialize Statping
|
||||
func InitApp() {
|
||||
SelectCore()
|
||||
InsertNotifierDB()
|
||||
CoreApp.SelectAllServices(true)
|
||||
checkServices()
|
||||
AttachNotifiers()
|
||||
CoreApp.Notifications = notifier.AllCommunications
|
||||
CoreApp.Integrations = integrations.Integrations
|
||||
go DatabaseMaintence()
|
||||
SetupMode = false
|
||||
}
|
||||
|
||||
// InsertNotifierDB inject the Statping database instance to the Notifier package
|
||||
func InsertNotifierDB() error {
|
||||
if DbSession == nil {
|
||||
err := CoreApp.Connect(false, utils.Directory)
|
||||
if err != nil {
|
||||
return errors.New("database connection has not been created")
|
||||
}
|
||||
}
|
||||
notifier.SetDB(DbSession, CoreApp.Timezone)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCore will update the CoreApp variable inside of the 'core' table in database
|
||||
func UpdateCore(c *Core) (*Core, error) {
|
||||
db := coreDB().Update(&c)
|
||||
return c, db.Error
|
||||
}
|
||||
|
||||
// CurrentTime will return the current local time
|
||||
func (c Core) CurrentTime() string {
|
||||
t := time.Now().UTC()
|
||||
current := utils.Timezoner(t, c.Timezone)
|
||||
ansic := "Monday 03:04:05 PM"
|
||||
return current.Format(ansic)
|
||||
}
|
||||
|
||||
// Messages will return the current local time
|
||||
func (c Core) Messages() []*Message {
|
||||
var message []*Message
|
||||
messagesDb().Where("service = ?", 0).Limit(10).Find(&message)
|
||||
return message
|
||||
}
|
||||
|
||||
// UsingAssets will return true if /assets folder is present
|
||||
func (c Core) UsingAssets() bool {
|
||||
return source.UsingAssets(utils.Directory)
|
||||
}
|
||||
|
||||
// SassVars opens the file /assets/scss/variables.scss to be edited in Theme
|
||||
func (c Core) SassVars() string {
|
||||
if !source.UsingAssets(utils.Directory) {
|
||||
return ""
|
||||
}
|
||||
return source.OpenAsset(utils.Directory, "scss/variables.scss")
|
||||
}
|
||||
|
||||
// BaseSASS is the base design , this opens the file /assets/scss/base.scss to be edited in Theme
|
||||
func (c Core) BaseSASS() string {
|
||||
if !source.UsingAssets(utils.Directory) {
|
||||
return ""
|
||||
}
|
||||
return source.OpenAsset(utils.Directory, "scss/base.scss")
|
||||
}
|
||||
|
||||
// MobileSASS is the -webkit responsive custom css designs. This opens the
|
||||
// file /assets/scss/mobile.scss to be edited in Theme
|
||||
func (c Core) MobileSASS() string {
|
||||
if !source.UsingAssets(utils.Directory) {
|
||||
return ""
|
||||
}
|
||||
return source.OpenAsset(utils.Directory, "scss/mobile.scss")
|
||||
}
|
||||
|
||||
// AllOnline will be true if all services are online
|
||||
func (c Core) AllOnline() bool {
|
||||
for _, s := range CoreApp.Services {
|
||||
if !s.Select().Online {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectCore will return the CoreApp global variable and the settings/configs for Statping
|
||||
func SelectCore() (*Core, error) {
|
||||
if DbSession == nil {
|
||||
log.Traceln("database has not been initiated yet.")
|
||||
return nil, errors.New("database has not been initiated yet.")
|
||||
}
|
||||
exists := DbSession.HasTable("core")
|
||||
if !exists {
|
||||
log.Errorf("core database has not been setup yet, does not have the 'core' table")
|
||||
return nil, errors.New("core database has not been setup yet.")
|
||||
}
|
||||
db := coreDB().First(&CoreApp)
|
||||
if db.Error != nil {
|
||||
return nil, db.Error
|
||||
}
|
||||
CoreApp.Version = VERSION
|
||||
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
|
||||
return CoreApp, db.Error
|
||||
}
|
||||
|
||||
// GetLocalIP returns the non loopback local IP of the host
|
||||
func GetLocalIP() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return "http://localhost"
|
||||
}
|
||||
for _, address := range addrs {
|
||||
// check the address type and if it is not a loopback the display it
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return fmt.Sprintf("http://%v", ipnet.IP.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return "http://localhost"
|
||||
}
|
||||
|
||||
// AttachNotifiers will attach all the notifier's into the system
|
||||
func AttachNotifiers() error {
|
||||
return notifier.AddNotifiers(
|
||||
notifiers.Command,
|
||||
notifiers.Discorder,
|
||||
notifiers.Emailer,
|
||||
notifiers.LineNotify,
|
||||
notifiers.Mobile,
|
||||
notifiers.Slacker,
|
||||
notifiers.Telegram,
|
||||
notifiers.Twilio,
|
||||
notifiers.Webhook,
|
||||
)
|
||||
}
|
||||
|
||||
// ServiceOrder will reorder the services based on 'order_id' (Order)
|
||||
type ServiceOrder []types.ServiceInterface
|
||||
|
||||
// Sort interface for resroting the Services in order
|
||||
func (c ServiceOrder) Len() int { return len(c) }
|
||||
func (c ServiceOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ServiceOrder) Less(i, j int) bool { return c[i].(*Service).Order < c[j].(*Service).Order }
|
|
@ -1,149 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
dir string
|
||||
skipNewDb bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
dir = utils.Directory
|
||||
utils.InitLogs()
|
||||
source.Assets()
|
||||
skipNewDb = false
|
||||
SampleHits = 480
|
||||
}
|
||||
|
||||
func TestNewCore(t *testing.T) {
|
||||
err := TmpRecords("core.db")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, CoreApp)
|
||||
}
|
||||
|
||||
func TestDbConfig_Save(t *testing.T) {
|
||||
t.SkipNow()
|
||||
//if skipNewDb {
|
||||
// t.SkipNow()
|
||||
//}
|
||||
//var err error
|
||||
//Configs = &DbConfig{
|
||||
// DbConn: "sqlite",
|
||||
// Project: "Tester",
|
||||
// Location: dir,
|
||||
//}
|
||||
//Configs, err = Configs.Save()
|
||||
//assert.Nil(t, err)
|
||||
//assert.Equal(t, "sqlite", Configs.DbConn)
|
||||
//assert.NotEmpty(t, Configs.ApiKey)
|
||||
//assert.NotEmpty(t, Configs.ApiSecret)
|
||||
}
|
||||
|
||||
func TestLoadDbConfig(t *testing.T) {
|
||||
Configs, err := LoadConfigFile(dir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "sqlite", Configs.DbConn)
|
||||
}
|
||||
|
||||
func TestDbConnection(t *testing.T) {
|
||||
err := CoreApp.Connect(false, dir)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestDropDatabase(t *testing.T) {
|
||||
t.SkipNow()
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
err := CoreApp.DropDatabase()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSeedSchemaDatabase(t *testing.T) {
|
||||
t.SkipNow()
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
err := CoreApp.CreateDatabase()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestMigrateDatabase(t *testing.T) {
|
||||
t.SkipNow()
|
||||
err := CoreApp.MigrateDatabase()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSeedDatabase(t *testing.T) {
|
||||
t.SkipNow()
|
||||
err := InsertLargeSampleData()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestReLoadDbConfig(t *testing.T) {
|
||||
err := CoreApp.Connect(false, dir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "sqlite", CoreApp.Config.DbConn)
|
||||
}
|
||||
|
||||
func TestSelectCore(t *testing.T) {
|
||||
core, err := SelectCore()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Statping Sample Data", core.Name)
|
||||
}
|
||||
|
||||
func TestInsertNotifierDB(t *testing.T) {
|
||||
t.SkipNow()
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
err := InsertNotifierDB()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestEnvToConfig(t *testing.T) {
|
||||
os.Setenv("DB_CONN", "sqlite")
|
||||
os.Setenv("DB_USER", "")
|
||||
os.Setenv("DB_PASS", "")
|
||||
os.Setenv("DB_DATABASE", "")
|
||||
os.Setenv("NAME", "Testing")
|
||||
os.Setenv("DOMAIN", "http://localhost:8080")
|
||||
os.Setenv("DESCRIPTION", "Testing Statping")
|
||||
os.Setenv("ADMIN_USER", "admin")
|
||||
os.Setenv("ADMIN_PASS", "admin123")
|
||||
os.Setenv("VERBOSE", "1")
|
||||
config, err := EnvToConfig()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, config.DbConn, "sqlite")
|
||||
assert.Equal(t, config.Domain, "http://localhost:8080")
|
||||
assert.Equal(t, config.Description, "Testing Statping")
|
||||
assert.Equal(t, config.Username, "admin")
|
||||
assert.Equal(t, config.Password, "admin123")
|
||||
}
|
||||
|
||||
func TestGetLocalIP(t *testing.T) {
|
||||
ip := GetLocalIP()
|
||||
assert.Contains(t, ip, "http://")
|
||||
}
|
417
core/database.go
417
core/database.go
|
@ -1,417 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// DbSession stores the Statping database session
|
||||
DbSession *gorm.DB
|
||||
DbModels []interface{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}}
|
||||
|
||||
gorm.NowFunc = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
}
|
||||
|
||||
// DbConfig stores the config.yml file for the statup configuration
|
||||
type DbConfig types.DbConfig
|
||||
|
||||
// failuresDB returns the 'failures' database column
|
||||
func failuresDB() *gorm.DB {
|
||||
return DbSession.Model(&types.Failure{})
|
||||
}
|
||||
|
||||
// hitsDB returns the 'hits' database column
|
||||
func hitsDB() *gorm.DB {
|
||||
return DbSession.Model(&types.Hit{})
|
||||
}
|
||||
|
||||
// servicesDB returns the 'services' database column
|
||||
func servicesDB() *gorm.DB {
|
||||
return DbSession.Model(&types.Service{})
|
||||
}
|
||||
|
||||
// coreDB returns the single column 'core'
|
||||
func coreDB() *gorm.DB {
|
||||
return DbSession.Table("core").Model(&CoreApp)
|
||||
}
|
||||
|
||||
// usersDB returns the 'users' database column
|
||||
func usersDB() *gorm.DB {
|
||||
return DbSession.Model(&types.User{})
|
||||
}
|
||||
|
||||
// checkinDB returns the Checkin records for a service
|
||||
func checkinDB() *gorm.DB {
|
||||
return DbSession.Model(&types.Checkin{})
|
||||
}
|
||||
|
||||
// checkinHitsDB returns the Checkin Hits records for a service
|
||||
func checkinHitsDB() *gorm.DB {
|
||||
return DbSession.Model(&types.CheckinHit{})
|
||||
}
|
||||
|
||||
// messagesDb returns the Checkin records for a service
|
||||
func messagesDb() *gorm.DB {
|
||||
return DbSession.Model(&types.Message{})
|
||||
}
|
||||
|
||||
// messagesDb returns the Checkin records for a service
|
||||
func groupsDb() *gorm.DB {
|
||||
return DbSession.Model(&types.Group{})
|
||||
}
|
||||
|
||||
// incidentsDB returns the 'incidents' database column
|
||||
func incidentsDB() *gorm.DB {
|
||||
return DbSession.Model(&types.Incident{})
|
||||
}
|
||||
|
||||
// incidentsUpdatesDB returns the 'incidents updates' database column
|
||||
func incidentsUpdatesDB() *gorm.DB {
|
||||
return DbSession.Model(&types.IncidentUpdate{})
|
||||
}
|
||||
|
||||
// HitsBetween returns the gorm database query for a collection of service hits between a time range
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) *gorm.DB {
|
||||
selector := Dbtimestamp(group, column)
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||
} else {
|
||||
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
|
||||
}
|
||||
}
|
||||
|
||||
// CloseDB will close the database connection if available
|
||||
func CloseDB() {
|
||||
if DbSession != nil {
|
||||
DbSession.DB().Close()
|
||||
}
|
||||
}
|
||||
|
||||
//// AfterFind for Core will set the timezone
|
||||
//func (c *Core) AfterFind() (err error) {
|
||||
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
// c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Service will set the timezone
|
||||
//func (s *Service) AfterFind() (err error) {
|
||||
// s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone)
|
||||
// s.UpdatedAt = utils.Timezoner(s.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Hit will set the timezone
|
||||
//func (h *Hit) AfterFind() (err error) {
|
||||
// h.CreatedAt = utils.Timezoner(h.CreatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Failure will set the timezone
|
||||
//func (f *Failure) AfterFind() (err error) {
|
||||
// f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for USer will set the timezone
|
||||
//func (u *User) AfterFind() (err error) {
|
||||
// u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
|
||||
// u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Checkin will set the timezone
|
||||
//func (c *Checkin) AfterFind() (err error) {
|
||||
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
// c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for checkinHit will set the timezone
|
||||
//func (c *CheckinHit) AfterFind() (err error) {
|
||||
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Message will set the timezone
|
||||
//func (u *Message) AfterFind() (err error) {
|
||||
// u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
|
||||
// u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
|
||||
// u.StartOn = utils.Timezoner(u.StartOn.UTC(), CoreApp.Timezone)
|
||||
// u.EndOn = utils.Timezoner(u.EndOn.UTC(), CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
|
||||
// InsertCore create the single row for the Core settings in Statping
|
||||
func (c *Core) InsertCore(db *types.DbConfig) (*Core, error) {
|
||||
CoreApp = &Core{Core: &types.Core{
|
||||
Name: db.Project,
|
||||
Description: db.Description,
|
||||
ConfigFile: "config.yml",
|
||||
ApiKey: utils.NewSHA1Hash(9),
|
||||
ApiSecret: utils.NewSHA1Hash(16),
|
||||
Domain: db.Domain,
|
||||
MigrationId: time.Now().Unix(),
|
||||
Config: db,
|
||||
}}
|
||||
query := coreDB().Create(&CoreApp)
|
||||
return CoreApp, query.Error
|
||||
}
|
||||
|
||||
func findDbFile() string {
|
||||
if CoreApp.Config.SqlFile != "" {
|
||||
return CoreApp.Config.SqlFile
|
||||
}
|
||||
filename := types.SqliteFilename
|
||||
err := filepath.Walk(utils.Directory, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(path) == ".db" {
|
||||
filename = info.Name()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
// Connect will attempt to connect to the sqlite, postgres, or mysql database
|
||||
func (c *Core) Connect(retry bool, location string) error {
|
||||
postgresSSL := os.Getenv("POSTGRES_SSLMODE")
|
||||
if DbSession != nil {
|
||||
return nil
|
||||
}
|
||||
var conn, dbType string
|
||||
var err error
|
||||
dbType = CoreApp.Config.DbConn
|
||||
if CoreApp.Config.DbPort == 0 {
|
||||
CoreApp.Config.DbPort = defaultPort(dbType)
|
||||
}
|
||||
switch dbType {
|
||||
case "sqlite":
|
||||
sqlFilename := findDbFile()
|
||||
conn = sqlFilename
|
||||
log.Infof("SQL database file at: %v/%v", utils.Directory, conn)
|
||||
dbType = "sqlite3"
|
||||
case "mysql":
|
||||
host := fmt.Sprintf("%v:%v", CoreApp.Config.DbHost, CoreApp.Config.DbPort)
|
||||
conn = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8&parseTime=True&loc=UTC&time_zone=%%27UTC%%27", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
||||
case "postgres":
|
||||
sslMode := "disable"
|
||||
if postgresSSL != "" {
|
||||
sslMode = postgresSSL
|
||||
}
|
||||
conn = fmt.Sprintf("host=%v port=%v user=%v dbname=%v password=%v timezone=UTC sslmode=%v", CoreApp.Config.DbHost, CoreApp.Config.DbPort, CoreApp.Config.DbUser, CoreApp.Config.DbData, CoreApp.Config.DbPass, sslMode)
|
||||
case "mssql":
|
||||
host := fmt.Sprintf("%v:%v", CoreApp.Config.DbHost, CoreApp.Config.DbPort)
|
||||
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
||||
}
|
||||
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
|
||||
dbSession, err := gorm.Open(dbType, conn)
|
||||
if err != nil {
|
||||
log.Debugln(fmt.Sprintf("Database connection error %v", err))
|
||||
if retry {
|
||||
log.Errorln(fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", CoreApp.Config.DbHost))
|
||||
return c.waitForDb()
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.WithFields(utils.ToFields(dbSession)).Debugln("connected to database")
|
||||
|
||||
dbSession.DB().SetMaxOpenConns(5)
|
||||
dbSession.DB().SetMaxIdleConns(5)
|
||||
dbSession.DB().SetConnMaxLifetime(1 * time.Minute)
|
||||
|
||||
if dbSession.DB().Ping() == nil {
|
||||
DbSession = dbSession
|
||||
if utils.VerboseMode >= 4 {
|
||||
DbSession.LogMode(true).Debug().SetLogger(log)
|
||||
}
|
||||
log.Infoln(fmt.Sprintf("Database %v connection was successful.", dbType))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// waitForDb will sleep for 5 seconds and try to connect to the database again
|
||||
func (c *Core) waitForDb() error {
|
||||
time.Sleep(5 * time.Second)
|
||||
return c.Connect(true, utils.Directory)
|
||||
}
|
||||
|
||||
// DatabaseMaintence will automatically delete old records from 'failures' and 'hits'
|
||||
// this function is currently set to delete records 7+ days old every 60 minutes
|
||||
func DatabaseMaintence() {
|
||||
for range time.Tick(60 * time.Minute) {
|
||||
log.Infoln("Checking for database records older than 3 months...")
|
||||
since := time.Now().AddDate(0, -3, 0).UTC()
|
||||
DeleteAllSince("failures", since)
|
||||
DeleteAllSince("hits", since)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteAllSince will delete a specific table's records based on a time.
|
||||
func DeleteAllSince(table string, date time.Time) {
|
||||
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
|
||||
db := DbSession.Exec(sql)
|
||||
if db.Error != nil {
|
||||
log.Warnln(db.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// Update will save the config.yml file
|
||||
func (c *Core) UpdateConfig() error {
|
||||
var err error
|
||||
config, err := os.Create(utils.Directory + "/config.yml")
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
data, err := yaml.Marshal(c.Config)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
config.WriteString(string(data))
|
||||
config.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Save will initially create the config.yml file
|
||||
func (c *Core) SaveConfig(configs *types.DbConfig) (*types.DbConfig, error) {
|
||||
config, err := os.Create(utils.Directory + "/config.yml")
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
defer config.Close()
|
||||
log.WithFields(utils.ToFields(configs)).Debugln("saving config file at: " + utils.Directory + "/config.yml")
|
||||
c.Config = configs
|
||||
c.Config.ApiKey = utils.NewSHA1Hash(16)
|
||||
c.Config.ApiSecret = utils.NewSHA1Hash(16)
|
||||
data, err := yaml.Marshal(configs)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
config.WriteString(string(data))
|
||||
log.WithFields(utils.ToFields(configs)).Infoln("saved config file at: " + utils.Directory + "/config.yml")
|
||||
return c.Config, err
|
||||
}
|
||||
|
||||
// CreateCore will initialize the global variable 'CoreApp". This global variable contains most of Statping app.
|
||||
func (c *Core) CreateCore() *Core {
|
||||
newCore := &types.Core{
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
ConfigFile: utils.Directory + "/config.yml",
|
||||
ApiKey: c.ApiKey,
|
||||
ApiSecret: c.ApiSecret,
|
||||
Domain: c.Domain,
|
||||
MigrationId: time.Now().Unix(),
|
||||
}
|
||||
db := coreDB().Create(&newCore)
|
||||
if db.Error == nil {
|
||||
CoreApp = &Core{Core: newCore}
|
||||
}
|
||||
CoreApp, err := SelectCore()
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
return CoreApp
|
||||
}
|
||||
|
||||
// DropDatabase will DROP each table Statping created
|
||||
func (c *Core) DropDatabase() error {
|
||||
log.Infoln("Dropping Database Tables...")
|
||||
err := DbSession.DropTableIfExists("checkins")
|
||||
err = DbSession.DropTableIfExists("checkin_hits")
|
||||
err = DbSession.DropTableIfExists("notifications")
|
||||
err = DbSession.DropTableIfExists("core")
|
||||
err = DbSession.DropTableIfExists("failures")
|
||||
err = DbSession.DropTableIfExists("hits")
|
||||
err = DbSession.DropTableIfExists("services")
|
||||
err = DbSession.DropTableIfExists("users")
|
||||
err = DbSession.DropTableIfExists("messages")
|
||||
err = DbSession.DropTableIfExists("incidents")
|
||||
err = DbSession.DropTableIfExists("incident_updates")
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// CreateDatabase will CREATE TABLES for each of the Statping elements
|
||||
func (c *Core) CreateDatabase() error {
|
||||
var err error
|
||||
log.Infoln("Creating Database Tables...")
|
||||
for _, table := range DbModels {
|
||||
if err := DbSession.CreateTable(table); err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
log.Infoln("Statping Database Created")
|
||||
return err
|
||||
}
|
||||
|
||||
// MigrateDatabase will migrate the database structure to current version.
|
||||
// This function will NOT remove previous records, tables or columns from the database.
|
||||
// If this function has an issue, it will ROLLBACK to the previous state.
|
||||
func (c *Core) MigrateDatabase() error {
|
||||
log.Infoln("Migrating Database Tables...")
|
||||
tx := DbSession.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
if tx.Error != nil {
|
||||
log.Errorln(tx.Error)
|
||||
return tx.Error
|
||||
}
|
||||
for _, table := range DbModels {
|
||||
tx = tx.AutoMigrate(table)
|
||||
}
|
||||
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error != nil {
|
||||
tx.Rollback()
|
||||
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
|
||||
return tx.Error
|
||||
}
|
||||
log.Infoln("Statping Database Migrated")
|
||||
return tx.Commit().Error
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// Package core contains the main functionality of Statping. This includes everything for
|
||||
// Services, Hits, Failures, Users, service checking mechanisms, databases, and notifiers
|
||||
// in the notifier package
|
||||
//
|
||||
// More info on: https://github.com/hunterlong/statping
|
||||
package core
|
|
@ -1,82 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// ExportChartsJs renders the charts for the index page
|
||||
func ExportChartsJs() string {
|
||||
render, err := source.JsBox.String("charts.js")
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
t := template.New("charts")
|
||||
t.Funcs(template.FuncMap{
|
||||
"safe": func(html string) template.HTML {
|
||||
return template.HTML(html)
|
||||
},
|
||||
})
|
||||
t.Parse(render)
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, CoreApp.Services); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
result := tpl.String()
|
||||
return result
|
||||
}
|
||||
|
||||
type ExportData struct {
|
||||
Core *types.Core `json:"core"`
|
||||
Services []types.ServiceInterface `json:"services"`
|
||||
Messages []*Message `json:"messages"`
|
||||
Checkins []*Checkin `json:"checkins"`
|
||||
Users []*User `json:"users"`
|
||||
Groups []*Group `json:"groups"`
|
||||
Notifiers []types.AllNotifiers `json:"notifiers"`
|
||||
}
|
||||
|
||||
// ExportSettings will export a JSON file containing all of the settings below:
|
||||
// - Core
|
||||
// - Notifiers
|
||||
// - Checkins
|
||||
// - Users
|
||||
// - Services
|
||||
// - Groups
|
||||
// - Messages
|
||||
func ExportSettings() ([]byte, error) {
|
||||
users, err := SelectAllUsers()
|
||||
messages, err := SelectMessages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := ExportData{
|
||||
Core: CoreApp.Core,
|
||||
Notifiers: CoreApp.Notifications,
|
||||
Checkins: AllCheckins(),
|
||||
Users: users,
|
||||
Services: CoreApp.Services,
|
||||
Groups: SelectGroups(true, true),
|
||||
Messages: messages,
|
||||
}
|
||||
export, err := json.Marshal(data)
|
||||
return export, err
|
||||
}
|
215
core/failures.go
215
core/failures.go
|
@ -1,215 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Failure struct {
|
||||
*types.Failure
|
||||
}
|
||||
|
||||
const (
|
||||
limitedFailures = 32
|
||||
limitedHits = 32
|
||||
)
|
||||
|
||||
// CreateFailure will create a new Failure record for a service
|
||||
func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
|
||||
f.Service = s.Id
|
||||
row := failuresDB().Create(f)
|
||||
if row.Error != nil {
|
||||
log.Errorln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
sort.Sort(types.FailSort(s.Failures))
|
||||
//s.Failures = append(s.Failures, f)
|
||||
if len(s.Failures) > limitedFailures {
|
||||
s.Failures = s.Failures[1:]
|
||||
}
|
||||
return f.Id, row.Error
|
||||
}
|
||||
|
||||
// AllFailures will return all failures attached to a service
|
||||
func (s *Service) AllFailures() []*Failure {
|
||||
var fails []*Failure
|
||||
col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc")
|
||||
err := col.Find(&fails)
|
||||
if err.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||
return nil
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// DeleteFailures will delete all failures for a service
|
||||
func (s *Service) DeleteFailures() {
|
||||
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
|
||||
if err.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err))
|
||||
}
|
||||
s.Failures = nil
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedFailures(amount int64) []*Failure {
|
||||
var failArr []*Failure
|
||||
failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
|
||||
return failArr
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedCheckinFailures(amount int64) []*Failure {
|
||||
var failArr []*Failure
|
||||
failuresDB().Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
|
||||
return failArr
|
||||
}
|
||||
|
||||
// Ago returns a human readable timestamp for a Failure
|
||||
func (f *Failure) Ago() string {
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().UTC(), f.CreatedAt)
|
||||
return got
|
||||
}
|
||||
|
||||
// Select returns a *types.Failure
|
||||
func (f *Failure) Select() *types.Failure {
|
||||
return f.Failure
|
||||
}
|
||||
|
||||
// Delete will remove a Failure record from the database
|
||||
func (f *Failure) Delete() error {
|
||||
db := failuresDB().Delete(f)
|
||||
return db.Error
|
||||
}
|
||||
|
||||
// Count24HFailures returns the amount of failures for a service within the last 24 hours
|
||||
func (c *Core) Count24HFailures() uint64 {
|
||||
var count uint64
|
||||
for _, s := range CoreApp.Services {
|
||||
service := s.(*Service)
|
||||
fails, _ := service.TotalFailures24()
|
||||
count += fails
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// CountFailures returns the total count of failures for all services
|
||||
func CountFailures() uint64 {
|
||||
var count uint64
|
||||
err := failuresDB().Count(&count)
|
||||
if err.Error != nil {
|
||||
log.Warnln(err.Error)
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// TotalFailuresOnDate returns the total amount of failures for a service on a specific time/date
|
||||
func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
date := ago.UTC().Format("2006-01-02 00:00:00")
|
||||
dateend := ago.UTC().Format("2006-01-02") + " 23:59:59"
|
||||
rows := failuresDB().Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
}
|
||||
|
||||
// TotalFailures24 returns the amount of failures for a service within the last 24 hours
|
||||
func (s *Service) TotalFailures24() (uint64, error) {
|
||||
ago := time.Now().UTC().Add(-24 * time.Hour)
|
||||
return s.TotalFailuresSince(ago)
|
||||
}
|
||||
|
||||
// TotalFailures returns the total amount of failures for a service
|
||||
func (s *Service) TotalFailures() (uint64, error) {
|
||||
var count uint64
|
||||
rows := failuresDB().Where("service = ?", s.Id)
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
}
|
||||
|
||||
// FailuresDaysAgo returns the amount of failures since days ago
|
||||
func (s *Service) FailuresDaysAgo(days int) uint64 {
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
count, _ := s.TotalFailuresSince(ago)
|
||||
return count
|
||||
}
|
||||
|
||||
// TotalFailuresSince returns the total amount of failures for a service since a specific time/date
|
||||
func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'")
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
}
|
||||
|
||||
// ParseError returns a human readable error for a Failure
|
||||
func (f *Failure) ParseError() string {
|
||||
if f.Method == "checkin" {
|
||||
return fmt.Sprintf("Checkin is Offline")
|
||||
}
|
||||
err := strings.Contains(f.Issue, "connection reset by peer")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Reset")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "operation timed out")
|
||||
if err {
|
||||
return fmt.Sprintf("HTTP Request Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "x509: certificate is valid")
|
||||
if err {
|
||||
return fmt.Sprintf("SSL Certificate invalid")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "no such host")
|
||||
if err {
|
||||
return fmt.Sprintf("Domain is offline or not found")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "HTTP Status Code")
|
||||
if err {
|
||||
return fmt.Sprintf("Incorrect HTTP Status Code")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "connection refused")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Failed")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "can't assign requested address")
|
||||
if err {
|
||||
return fmt.Sprintf("Unable to Request Address")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "no route to host")
|
||||
if err {
|
||||
return fmt.Sprintf("Domain is offline or not found")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "i/o timeout")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body")
|
||||
if err {
|
||||
return fmt.Sprintf("Timed Out on Response Body")
|
||||
}
|
||||
return f.Issue
|
||||
}
|
101
core/groups.go
101
core/groups.go
|
@ -1,101 +0,0 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
*types.Group
|
||||
}
|
||||
|
||||
// Delete will remove a group
|
||||
func (g *Group) Delete() error {
|
||||
for _, s := range g.Services() {
|
||||
s.GroupId = 0
|
||||
s.Update(false)
|
||||
}
|
||||
err := groupsDb().Delete(g)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Create will create a group and insert it into the database
|
||||
func (g *Group) Create() (int64, error) {
|
||||
g.CreatedAt = time.Now().UTC()
|
||||
db := groupsDb().Create(g)
|
||||
return g.Id, db.Error
|
||||
}
|
||||
|
||||
// Update will update a group
|
||||
func (g *Group) Update() (int64, error) {
|
||||
g.UpdatedAt = time.Now().UTC()
|
||||
db := groupsDb().Update(g)
|
||||
return g.Id, db.Error
|
||||
}
|
||||
|
||||
// Services returns all services belonging to a group
|
||||
func (g *Group) Services() []*Service {
|
||||
var services []*Service
|
||||
for _, s := range Services() {
|
||||
if s.Select().GroupId == int(g.Id) {
|
||||
services = append(services, s.(*Service))
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
// VisibleServices returns all services based on authentication
|
||||
func (g *Group) VisibleServices(auth bool) []*Service {
|
||||
var services []*Service
|
||||
for _, g := range g.Services() {
|
||||
if !g.Public.Bool {
|
||||
if auth {
|
||||
services = append(services, g)
|
||||
}
|
||||
} else {
|
||||
services = append(services, g)
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
// SelectGroups returns all groups
|
||||
func SelectGroups(includeAll bool, auth bool) []*Group {
|
||||
var groups []*Group
|
||||
var validGroups []*Group
|
||||
groupsDb().Find(&groups).Order("order_id desc")
|
||||
for _, g := range groups {
|
||||
if !g.Public.Bool {
|
||||
if auth {
|
||||
validGroups = append(validGroups, g)
|
||||
}
|
||||
} else {
|
||||
validGroups = append(validGroups, g)
|
||||
}
|
||||
}
|
||||
sort.Sort(GroupOrder(validGroups))
|
||||
if includeAll {
|
||||
emptyGroup := &Group{&types.Group{Id: 0, Public: types.NewNullBool(true)}}
|
||||
validGroups = append(validGroups, emptyGroup)
|
||||
}
|
||||
return validGroups
|
||||
}
|
||||
|
||||
// SelectGroup returns a *core.Group
|
||||
func SelectGroup(id int64) *Group {
|
||||
for _, g := range SelectGroups(true, true) {
|
||||
if g.Id == id {
|
||||
return g
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GroupOrder will reorder the groups based on 'order_id' (Order)
|
||||
type GroupOrder []*Group
|
||||
|
||||
// Sort interface for resorting the Groups in order
|
||||
func (c GroupOrder) Len() int { return len(c) }
|
||||
func (c GroupOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c GroupOrder) Less(i, j int) bool { return c[i].Order < c[j].Order }
|
91
core/hits.go
91
core/hits.go
|
@ -1,91 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Hit struct {
|
||||
*types.Hit
|
||||
}
|
||||
|
||||
// CreateHit will create a new 'hit' record in the database for a successful/online service
|
||||
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
|
||||
db := hitsDB().Create(&h)
|
||||
if db.Error != nil {
|
||||
log.Errorln(db.Error)
|
||||
return 0, db.Error
|
||||
}
|
||||
return h.Id, db.Error
|
||||
}
|
||||
|
||||
// CountHits returns a int64 for all hits for a service
|
||||
func (s *Service) CountHits() (int64, error) {
|
||||
var hits int64
|
||||
col := hitsDB().Where("service = ?", s.Id)
|
||||
err := col.Count(&hits)
|
||||
return hits, err.Error
|
||||
}
|
||||
|
||||
// Hits returns all successful hits for a service
|
||||
func (s *Service) Hits() ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := hitsDB().Where("service = ?", s.Id).Order("id desc")
|
||||
err := col.Find(&hits)
|
||||
return hits, err.Error
|
||||
}
|
||||
|
||||
// LimitedHits returns the last 1024 successful/online 'hit' records for a service
|
||||
func (s *Service) LimitedHits(amount int64) ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := hitsDB().Where("service = ?", s.Id).Order("id desc").Limit(amount)
|
||||
err := col.Find(&hits)
|
||||
return reverseHits(hits), err.Error
|
||||
}
|
||||
|
||||
// reverseHits will reverse the service's hit slice
|
||||
func reverseHits(input []*types.Hit) []*types.Hit {
|
||||
if len(input) == 0 {
|
||||
return input
|
||||
}
|
||||
return append(reverseHits(input[1:]), input[0])
|
||||
}
|
||||
|
||||
// TotalHits returns the total amount of successful hits a service has
|
||||
func (s *Service) TotalHits() (uint64, error) {
|
||||
var count uint64
|
||||
col := hitsDB().Where("service = ?", s.Id).Count(&count)
|
||||
return count, col.Error
|
||||
}
|
||||
|
||||
// TotalHitsSince returns the total amount of hits based on a specific time/date
|
||||
func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
rows := hitsDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Count(&count)
|
||||
return count, rows.Error
|
||||
}
|
||||
|
||||
// Sum returns the added value Latency for all of the services successful hits.
|
||||
func (s *Service) Sum() float64 {
|
||||
var sum float64
|
||||
rows, _ := hitsDB().Where("service = ?", s.Id).Select("sum(latency) as total").Rows()
|
||||
for rows.Next() {
|
||||
rows.Scan(&sum)
|
||||
}
|
||||
return sum
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Incident struct {
|
||||
*types.Incident
|
||||
}
|
||||
|
||||
type IncidentUpdate struct {
|
||||
*types.IncidentUpdate
|
||||
}
|
||||
|
||||
// AllIncidents will return all incidents and updates recorded
|
||||
func AllIncidents() []*Incident {
|
||||
var incidents []*Incident
|
||||
incidentsDB().Find(&incidents).Order("id desc")
|
||||
for _, i := range incidents {
|
||||
var updates []*types.IncidentUpdate
|
||||
incidentsUpdatesDB().Find(&updates).Order("id desc")
|
||||
i.Updates = updates
|
||||
}
|
||||
return incidents
|
||||
}
|
||||
|
||||
// Incidents will return the all incidents for a service
|
||||
func (s *Service) Incidents() []*Incident {
|
||||
var incidentArr []*Incident
|
||||
incidentsDB().Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
|
||||
return incidentArr
|
||||
}
|
||||
|
||||
// AllUpdates will return the all updates for an incident
|
||||
func (i *Incident) AllUpdates() []*IncidentUpdate {
|
||||
var updatesArr []*IncidentUpdate
|
||||
incidentsUpdatesDB().Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
|
||||
return updatesArr
|
||||
}
|
||||
|
||||
// Delete will remove a incident
|
||||
func (i *Incident) Delete() error {
|
||||
err := incidentsDB().Delete(i)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Create will create a incident and insert it into the database
|
||||
func (i *Incident) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now().UTC()
|
||||
db := incidentsDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
||||
// Update will update a incident
|
||||
func (i *Incident) Update() (int64, error) {
|
||||
i.UpdatedAt = time.Now().UTC()
|
||||
db := incidentsDB().Update(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
||||
// Delete will remove a incident update
|
||||
func (i *IncidentUpdate) Delete() error {
|
||||
err := incidentsUpdatesDB().Delete(i)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Create will create a incident update and insert it into the database
|
||||
func (i *IncidentUpdate) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now().UTC()
|
||||
db := incidentsUpdatesDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const requiredSize = 17
|
||||
|
||||
type csvIntegration struct {
|
||||
*types.Integration
|
||||
}
|
||||
|
||||
var csvIntegrator = &csvIntegration{&types.Integration{
|
||||
ShortName: "csv",
|
||||
Name: "CSV File",
|
||||
Icon: "<i class=\"fas fa-file-csv\"></i>",
|
||||
Description: "Import multiple services from a CSV file. Please have your CSV file formatted with the correct amount of columns based on the <a href=\"https://raw.githubusercontent.com/hunterlong/statping/master/source/tmpl/bulk_import.csv\">example file on Github</a>.",
|
||||
Fields: []*types.IntegrationField{
|
||||
{
|
||||
Name: "input",
|
||||
Type: "textarea",
|
||||
Description: "",
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
var csvData [][]string
|
||||
|
||||
func (t *csvIntegration) Get() *types.Integration {
|
||||
return t.Integration
|
||||
}
|
||||
|
||||
func (t *csvIntegration) List() ([]*types.Service, error) {
|
||||
data := Value(t, "input").(string)
|
||||
buf := bytes.NewReader([]byte(data))
|
||||
r := csv.NewReader(buf)
|
||||
records, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var services []*types.Service
|
||||
for k, v := range records[1:] {
|
||||
s, err := commaToService(v)
|
||||
if err != nil {
|
||||
log.Errorf("error on line %v: %v", k, err)
|
||||
continue
|
||||
}
|
||||
services = append(services, s)
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// commaToService will convert a CSV comma delimited string slice to a Service type
|
||||
// this function is used for the bulk import services feature
|
||||
func commaToService(s []string) (*types.Service, error) {
|
||||
if len(s) != requiredSize {
|
||||
err := fmt.Errorf("file has %v columns of data, not the expected amount of %v columns for a service", len(s), requiredSize)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval, err := time.ParseDuration(s[4])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse internal duration: " + s[4])
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(s[9])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse timeout duration: " + s[9])
|
||||
}
|
||||
|
||||
allowNotifications, err := strconv.ParseBool(s[11])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse allow notifications boolean: " + s[11])
|
||||
}
|
||||
|
||||
public, err := strconv.ParseBool(s[12])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse public boolean: " + s[12])
|
||||
}
|
||||
|
||||
verifySsl, err := strconv.ParseBool(s[16])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse verifiy SSL boolean: " + s[16])
|
||||
}
|
||||
|
||||
newService := &types.Service{
|
||||
Name: s[0],
|
||||
Domain: s[1],
|
||||
Expected: types.NewNullString(s[2]),
|
||||
ExpectedStatus: int(utils.ToInt(s[3])),
|
||||
Interval: int(utils.ToInt(interval.Seconds())),
|
||||
Type: s[5],
|
||||
Method: s[6],
|
||||
PostData: types.NewNullString(s[7]),
|
||||
Port: int(utils.ToInt(s[8])),
|
||||
Timeout: int(utils.ToInt(timeout.Seconds())),
|
||||
AllowNotifications: types.NewNullBool(allowNotifications),
|
||||
Public: types.NewNullBool(public),
|
||||
GroupId: int(utils.ToInt(s[13])),
|
||||
Headers: types.NewNullString(s[14]),
|
||||
Permalink: types.NewNullString(s[15]),
|
||||
VerifySSL: types.NewNullBool(verifySsl),
|
||||
}
|
||||
|
||||
return newService, nil
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCsvFileIntegration(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("../../source/tmpl/bulk_import.csv")
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("Set Field Value", func(t *testing.T) {
|
||||
formPost := map[string][]string{}
|
||||
formPost["input"] = []string{string(data)}
|
||||
_, err = SetFields(csvIntegrator, formPost)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get Field Value", func(t *testing.T) {
|
||||
value := Value(csvIntegrator, "input").(string)
|
||||
assert.Equal(t, string(data), value)
|
||||
})
|
||||
|
||||
t.Run("List Services from CSV File", func(t *testing.T) {
|
||||
services, err := csvIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(services))
|
||||
})
|
||||
|
||||
t.Run("Confirm Services from CSV File", func(t *testing.T) {
|
||||
services, err := csvIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "Bulk Upload", services[0].Name)
|
||||
assert.Equal(t, "http://google.com", services[0].Domain)
|
||||
assert.Equal(t, 60, services[0].Interval)
|
||||
for _, s := range services {
|
||||
t.Log(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
dTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
type dockerIntegration struct {
|
||||
*types.Integration
|
||||
}
|
||||
|
||||
var dockerIntegrator = &dockerIntegration{&types.Integration{
|
||||
ShortName: "docker",
|
||||
Name: "Docker",
|
||||
Icon: "<i class=\"fab fa-docker\"></i>",
|
||||
Description: `Import multiple services from Docker by attaching the unix socket to Statping.
|
||||
You can also do this in Docker by setting <u>-v /var/run/docker.sock:/var/run/docker.sock</u> in the Statping Docker container.
|
||||
All of the containers with open TCP/UDP ports will be listed for you to choose which services you want to add. If you running Statping inside of a container,
|
||||
this container must be attached to all networks you want to communicate with.`,
|
||||
Fields: []*types.IntegrationField{
|
||||
{
|
||||
Name: "path",
|
||||
Description: "The absolute path to the Docker unix socket",
|
||||
Type: "text",
|
||||
Value: client.DefaultDockerHost,
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Description: "Version number of Docker server",
|
||||
Type: "text",
|
||||
Value: client.DefaultVersion,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
var cli *client.Client
|
||||
|
||||
func (t *dockerIntegration) Get() *types.Integration {
|
||||
return t.Integration
|
||||
}
|
||||
|
||||
func (t *dockerIntegration) List() ([]*types.Service, error) {
|
||||
var err error
|
||||
path := Value(t, "path").(string)
|
||||
version := Value(t, "version").(string)
|
||||
os.Setenv("DOCKER_HOST", path)
|
||||
os.Setenv("DOCKER_VERSION", version)
|
||||
cli, err = client.NewEnvClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
var services []*types.Service
|
||||
|
||||
containers, err := cli.ContainerList(context.Background(), dTypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, container := range containers {
|
||||
if container.State != "running" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range container.Ports {
|
||||
if v.IP == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
service := &types.Service{
|
||||
Name: container.Names[0][1:],
|
||||
Domain: v.IP,
|
||||
Type: v.Type,
|
||||
Port: int(v.PublicPort),
|
||||
Interval: 60,
|
||||
Timeout: 2,
|
||||
}
|
||||
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
}
|
||||
return services, nil
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDockerIntegration(t *testing.T) {
|
||||
|
||||
t.Run("Set Field Value", func(t *testing.T) {
|
||||
formPost := map[string][]string{}
|
||||
formPost["path"] = []string{"unix:///var/run/docker.sock"}
|
||||
formPost["version"] = []string{"1.25"}
|
||||
_, err := SetFields(csvIntegrator, formPost)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get Field Value", func(t *testing.T) {
|
||||
path := Value(dockerIntegrator, "path").(string)
|
||||
version := Value(dockerIntegrator, "version").(string)
|
||||
assert.Equal(t, "unix:///var/run/docker.sock", path)
|
||||
assert.Equal(t, "1.25", version)
|
||||
})
|
||||
|
||||
t.Run("List Services from Docker", func(t *testing.T) {
|
||||
services, err := dockerIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 0, len(services))
|
||||
})
|
||||
|
||||
t.Run("Confirm Services from Docker", func(t *testing.T) {
|
||||
services, err := dockerIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
for _, s := range services {
|
||||
t.Log(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
Integrations []types.Integrator
|
||||
log = utils.Log.WithField("type", "integration")
|
||||
)
|
||||
|
||||
func init() {
|
||||
Integrations = append(Integrations,
|
||||
csvIntegrator,
|
||||
dockerIntegrator,
|
||||
traefikIntegrator,
|
||||
)
|
||||
}
|
||||
|
||||
func Value(intg types.Integrator, fieldName string) interface{} {
|
||||
for _, v := range intg.Get().Fields {
|
||||
if fieldName == v.Name {
|
||||
return v.Value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetFields(intg types.Integrator, data map[string][]string) (*types.Integration, error) {
|
||||
i := intg.Get()
|
||||
for _, v := range i.Fields {
|
||||
if data[v.Name] != nil {
|
||||
v.Value = data[v.Name][0]
|
||||
}
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func Find(name string) (types.Integrator, error) {
|
||||
for _, i := range Integrations {
|
||||
obj := i.Get()
|
||||
if obj.ShortName == name {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New(name + " not found")
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegrations(t *testing.T) {
|
||||
|
||||
t.Run("Collect Integrations", func(t *testing.T) {
|
||||
amount := len(Integrations)
|
||||
assert.Equal(t, 3, amount)
|
||||
})
|
||||
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type traefikIntegration struct {
|
||||
*types.Integration
|
||||
}
|
||||
|
||||
var traefikIntegrator = &traefikIntegration{&types.Integration{
|
||||
ShortName: "traefik",
|
||||
Name: "Traefik",
|
||||
Icon: "<i class=\"fas fa-network-wired\"></i>",
|
||||
Description: ``,
|
||||
Fields: []*types.IntegrationField{
|
||||
{
|
||||
Name: "endpoint",
|
||||
Description: "The URL for the traefik API Endpoint",
|
||||
Type: "text",
|
||||
Value: "http://localhost:8080",
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Description: "Username for HTTP Basic Authentication",
|
||||
Type: "text",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Description: "Password for HTTP Basic Authentication",
|
||||
Type: "password",
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
func (t *traefikIntegration) Get() *types.Integration {
|
||||
return t.Integration
|
||||
}
|
||||
|
||||
func (t *traefikIntegration) List() ([]*types.Service, error) {
|
||||
var err error
|
||||
var services []*types.Service
|
||||
|
||||
endpoint := Value(t, "endpoint").(string)
|
||||
|
||||
httpServices, err := fetchMethod(endpoint, "http")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, httpServices...)
|
||||
|
||||
tcpServices, err := fetchMethod(endpoint, "tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, tcpServices...)
|
||||
|
||||
return services, err
|
||||
}
|
||||
|
||||
func fetchMethod(endpoint, method string) ([]*types.Service, error) {
|
||||
var traefikServices []traefikService
|
||||
var services []*types.Service
|
||||
d, _, err := utils.HttpRequest(endpoint+"/api/"+method+"/services", "GET", nil, []string{}, nil, 10*time.Second, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(d, &traefikServices); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, s := range traefikServices {
|
||||
log.Infoln(s)
|
||||
|
||||
for _, l := range s.LoadBalancer.Servers {
|
||||
|
||||
url, err := url.Parse(l.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &types.Service{
|
||||
Name: s.Name,
|
||||
Domain: url.Hostname(),
|
||||
Port: int(utils.ToInt(url.Port())),
|
||||
Type: method,
|
||||
Interval: 60,
|
||||
Timeout: 2,
|
||||
}
|
||||
services = append(services, service)
|
||||
|
||||
}
|
||||
}
|
||||
return services, err
|
||||
}
|
||||
|
||||
type traefikService struct {
|
||||
Status string `json:"status"`
|
||||
UsedBy []string `json:"usedBy"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
LoadBalancer struct {
|
||||
Servers []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"servers"`
|
||||
PassHostHeader bool `json:"passHostHeader"`
|
||||
} `json:"loadBalancer,omitempty"`
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTraefikIntegration(t *testing.T) {
|
||||
|
||||
t.Run("List Services from Traefik", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
services, err := traefikIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(services))
|
||||
})
|
||||
|
||||
t.Run("Confirm Services from Traefik", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
services, err := traefikIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
for _, s := range services {
|
||||
t.Log(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
*types.Message
|
||||
}
|
||||
|
||||
// SelectServiceMessages returns all messages for a service
|
||||
func SelectServiceMessages(id int64) []*Message {
|
||||
var message []*Message
|
||||
messagesDb().Where("service = ?", id).Limit(10).Find(&message)
|
||||
return message
|
||||
}
|
||||
|
||||
// ReturnMessage will convert *types.Message to *core.Message
|
||||
func ReturnMessage(m *types.Message) *Message {
|
||||
return &Message{m}
|
||||
}
|
||||
|
||||
// SelectMessages returns all messages
|
||||
func SelectMessages() ([]*Message, error) {
|
||||
var messages []*Message
|
||||
db := messagesDb().Find(&messages).Order("id desc")
|
||||
return messages, db.Error
|
||||
}
|
||||
|
||||
// SelectMessage returns a Message based on the ID passed
|
||||
func SelectMessage(id int64) (*Message, error) {
|
||||
var message Message
|
||||
db := messagesDb().Where("id = ?", id).Find(&message)
|
||||
return &message, db.Error
|
||||
}
|
||||
|
||||
func (m *Message) Service() *Service {
|
||||
if m.ServiceId == 0 {
|
||||
return nil
|
||||
}
|
||||
return SelectService(m.ServiceId)
|
||||
}
|
||||
|
||||
// Create will create a Message and insert it into the database
|
||||
func (m *Message) Create() (int64, error) {
|
||||
m.CreatedAt = time.Now().UTC()
|
||||
db := messagesDb().Create(m)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
return 0, db.Error
|
||||
}
|
||||
return m.Id, nil
|
||||
}
|
||||
|
||||
// Delete will delete a Message from database
|
||||
func (m *Message) Delete() error {
|
||||
db := messagesDb().Delete(m)
|
||||
return db.Error
|
||||
}
|
||||
|
||||
// Update will update a Message in the database
|
||||
func (m *Message) Update() (*Message, error) {
|
||||
db := messagesDb().Update(m)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
return nil, db.Error
|
||||
}
|
||||
return m, nil
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
allowedVars = []string{"host", "username", "password", "port", "api_key", "api_secret", "var1", "var2"}
|
||||
)
|
||||
|
||||
func checkNotifierForm(n Notifier) error {
|
||||
notifier := n.Select()
|
||||
for _, f := range notifier.Form {
|
||||
contains := contains(f.DbField, allowedVars)
|
||||
if !contains {
|
||||
return fmt.Errorf("the DbField '%v' is not allowed, allowed vars: %v", f.DbField, allowedVars)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(s string, arr []string) bool {
|
||||
for _, v := range arr {
|
||||
if strings.ToLower(s) == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
// Package notifier contains the main functionality for the Statping Notification system
|
||||
//
|
||||
// Example Notifier
|
||||
//
|
||||
// Below is an example of a Notifier with multiple Form values to custom your inputs. Place your notifier go file
|
||||
// into the /notifiers/ directory and follow the example below.
|
||||
//
|
||||
// type ExampleNotifier struct {
|
||||
// *Notification
|
||||
// }
|
||||
//
|
||||
// var example = &ExampleNotifier{&Notification{
|
||||
// Method: "example",
|
||||
// Title: "Example Notifier",
|
||||
// Description: "This is an example of a notifier for Statping!",
|
||||
// Author: "Hunter Long",
|
||||
// AuthorUrl: "https://github.com/hunterlong",
|
||||
// Delay: time.Duration(3 * time.Second),
|
||||
// Limits: 7,
|
||||
// Form: []NotificationForm{{
|
||||
// Type: "text",
|
||||
// Title: "Host",
|
||||
// Placeholder: "Insert your Host here.",
|
||||
// DbField: "host",
|
||||
// SmallText: "this is where you would put the host",
|
||||
// }, {
|
||||
// Type: "text",
|
||||
// Title: "Username",
|
||||
// Placeholder: "Insert your Username here.",
|
||||
// DbField: "username",
|
||||
// }, {
|
||||
// Type: "password",
|
||||
// Title: "Password",
|
||||
// Placeholder: "Insert your Password here.",
|
||||
// DbField: "password",
|
||||
// }, {
|
||||
// Type: "number",
|
||||
// Title: "Port",
|
||||
// Placeholder: "Insert your Port here.",
|
||||
// DbField: "port",
|
||||
// }, {
|
||||
// Type: "text",
|
||||
// Title: "API Key",
|
||||
// Placeholder: "Insert your API Key here",
|
||||
// DbField: "api_key",
|
||||
// }, {
|
||||
// Type: "text",
|
||||
// Title: "API Secret",
|
||||
// Placeholder: "Insert your API Secret here",
|
||||
// DbField: "api_secret",
|
||||
// }, {
|
||||
// Type: "text",
|
||||
// Title: "Var 1",
|
||||
// Placeholder: "Insert your Var1 here",
|
||||
// DbField: "var1",
|
||||
// }, {
|
||||
// Type: "text",
|
||||
// Title: "Var2",
|
||||
// Placeholder: "Var2 goes here",
|
||||
// DbField: "var2",
|
||||
// }},
|
||||
// }}
|
||||
//
|
||||
// Load the Notifier
|
||||
//
|
||||
// Include the init() function with AddNotifier and your notification struct. This is ran on start of Statping
|
||||
// and will automatically create a new row in the database so the end user can save their own values.
|
||||
//
|
||||
// func init() {
|
||||
// AddNotifier(example)
|
||||
// }
|
||||
//
|
||||
// Required Methods for Notifier Interface
|
||||
//
|
||||
// Below are the required methods to have your notifier implement the Notifier interface. The Send method
|
||||
// will be where you would include the logic for your notification.
|
||||
//
|
||||
// // REQUIRED
|
||||
// func (n *ExampleNotifier) Send(msg interface{}) error {
|
||||
// message := msg.(string)
|
||||
// fmt.Printf("i received this string: %v\n", message)
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // REQUIRED
|
||||
// func (n *ExampleNotifier) Select() *Notification {
|
||||
// return n.Notification
|
||||
// }
|
||||
//
|
||||
// // REQUIRED
|
||||
// func (n *ExampleNotifier) OnSave() error {
|
||||
// msg := fmt.Sprintf("received on save trigger")
|
||||
// n.AddQueue(msg)
|
||||
// return errors.New("onsave triggered")
|
||||
// }
|
||||
//
|
||||
// Basic Events for Notifier
|
||||
//
|
||||
// You must include OnSuccess and OnFailure methods for your notifier. Anytime a service is online or offline
|
||||
// these methods will be ran with the service corresponding to it.
|
||||
//
|
||||
// // REQUIRED - BASIC EVENT
|
||||
// func (n *ExampleNotifier) OnSuccess(s *types.Service) {
|
||||
// msg := fmt.Sprintf("received a count trigger for service: %v\n", s.Name)
|
||||
// n.AddQueue(msg)
|
||||
// }
|
||||
//
|
||||
// // REQUIRED - BASIC EVENT
|
||||
// func (n *ExampleNotifier) OnFailure(s *types.Service, f *types.Failure) {
|
||||
// msg := fmt.Sprintf("received a failure trigger for service: %v\n", s.Name)
|
||||
// n.AddQueue(msg)
|
||||
// }
|
||||
//
|
||||
// Additional Events
|
||||
//
|
||||
// You can implement your notifier to different types of events that are triggered. Checkout the wiki to
|
||||
// see more details and examples of how to build your own notifier.
|
||||
//
|
||||
// More info on: https://github.com/hunterlong/statping/wiki/Notifiers
|
||||
package notifier
|
|
@ -1,191 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
)
|
||||
|
||||
// OnSave will trigger a notifier when it has been saved - Notifier interface
|
||||
func OnSave(method string) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(Notifier)) {
|
||||
notifier := comm.(Notifier)
|
||||
if notifier.Select().Method == method {
|
||||
notifier.OnSave()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnFailure will be triggered when a service is failing - BasicEvents interface
|
||||
func OnFailure(s *types.Service, f *types.Failure) {
|
||||
if !s.AllowNotifications.Bool {
|
||||
return
|
||||
}
|
||||
|
||||
// check if User wants to receive every Status Change
|
||||
if s.UpdateNotify {
|
||||
// send only if User hasn't been already notified about the Downtime
|
||||
if !s.UserNotified {
|
||||
s.UserNotified = true
|
||||
goto sendMessages
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sendMessages:
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) {
|
||||
notifier := comm.(Notifier).Select()
|
||||
log.
|
||||
WithField("trigger", "OnFailure").
|
||||
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name))
|
||||
comm.(BasicEvents).OnFailure(s, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnSuccess will be triggered when a service is successful - BasicEvents interface
|
||||
func OnSuccess(s *types.Service) {
|
||||
if !s.AllowNotifications.Bool {
|
||||
return
|
||||
}
|
||||
|
||||
// check if User wants to receive every Status Change
|
||||
if s.UpdateNotify && s.UserNotified {
|
||||
s.UserNotified = false
|
||||
}
|
||||
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) {
|
||||
notifier := comm.(Notifier).Select()
|
||||
log.
|
||||
WithField("trigger", "OnSuccess").
|
||||
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name))
|
||||
comm.(BasicEvents).OnSuccess(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnNewService is triggered when a new service is created - ServiceEvents interface
|
||||
func OnNewService(s *types.Service) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.
|
||||
WithField("trigger", "OnNewService").
|
||||
Infoln(fmt.Sprintf("Sending new service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnNewService(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdatedService is triggered when a service is updated - ServiceEvents interface
|
||||
func OnUpdatedService(s *types.Service) {
|
||||
if !s.AllowNotifications.Bool {
|
||||
return
|
||||
}
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnUpdatedService(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnDeletedService is triggered when a service is deleted - ServiceEvents interface
|
||||
func OnDeletedService(s *types.Service) {
|
||||
if !s.AllowNotifications.Bool {
|
||||
return
|
||||
}
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnDeletedService(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnNewUser is triggered when a new user is created - UserEvents interface
|
||||
func OnNewUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending new user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnNewUser(u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdatedUser is triggered when a new user is updated - UserEvents interface
|
||||
func OnUpdatedUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnUpdatedUser(u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnDeletedUser is triggered when a new user is deleted - UserEvents interface
|
||||
func OnDeletedUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnDeletedUser(u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdatedCore is triggered when the CoreApp settings are saved - CoreEvents interface
|
||||
func OnUpdatedCore(c *types.Core) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated core notification"))
|
||||
comm.(CoreEvents).OnUpdatedCore(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnStart is triggered when the Statping service has started
|
||||
func OnStart(c *types.Core) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
comm.(CoreEvents).OnUpdatedCore(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnNewNotifier is triggered when a new notifier is loaded
|
||||
func OnNewNotifier(n *Notification) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
comm.(NotifierEvents).OnNewNotifier(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdatedNotifier is triggered when a notifier has been updated
|
||||
func OnUpdatedNotifier(n *Notification) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated notifier for %v", n.Id))
|
||||
comm.(NotifierEvents).OnUpdatedNotifier(n)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,324 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExampleNotifier is an example on how to use the Statping notifier struct
|
||||
type ExampleNotifier struct {
|
||||
*Notification
|
||||
}
|
||||
|
||||
// example is a example variable for a example notifier
|
||||
var example = &ExampleNotifier{&Notification{
|
||||
Method: METHOD,
|
||||
Host: "http://exmaplehost.com",
|
||||
Title: "Example",
|
||||
Description: "Example Notifier",
|
||||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
Delay: time.Duration(3 * time.Second),
|
||||
Limits: 7,
|
||||
Form: []NotificationForm{{
|
||||
Type: "text",
|
||||
Title: "Host",
|
||||
Placeholder: "Insert your Host here.",
|
||||
DbField: "host",
|
||||
SmallText: "this is where you would put the host",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "Username",
|
||||
Placeholder: "Insert your Username here.",
|
||||
DbField: "username",
|
||||
}, {
|
||||
Type: "password",
|
||||
Title: "Password",
|
||||
Placeholder: "Insert your Password here.",
|
||||
DbField: "password",
|
||||
}, {
|
||||
Type: "number",
|
||||
Title: "Port",
|
||||
Placeholder: "Insert your Port here.",
|
||||
DbField: "port",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "API Key",
|
||||
Placeholder: "Insert your API Key here",
|
||||
DbField: "api_key",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "API Secret",
|
||||
Placeholder: "Insert your API Secret here",
|
||||
DbField: "api_secret",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "Var 1",
|
||||
Placeholder: "Insert your Var1 here",
|
||||
DbField: "var1",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "Var2",
|
||||
Placeholder: "Var2 goes here",
|
||||
DbField: "var2",
|
||||
}},
|
||||
}}
|
||||
|
||||
// init will be ran when Statping is loaded, AddNotifier will add the notifier instance to the system
|
||||
func init() {
|
||||
dir = utils.Directory
|
||||
source.Assets()
|
||||
utils.InitLogs()
|
||||
injectDatabase()
|
||||
AddNotifiers(example)
|
||||
}
|
||||
|
||||
// Send is the main function to hold your notifier functionality
|
||||
func (n *ExampleNotifier) Send(msg interface{}) error {
|
||||
message := msg.(string)
|
||||
fmt.Printf("i received this string: %v\n", message)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) Select() *Notification {
|
||||
return n.Notification
|
||||
}
|
||||
|
||||
// OnSave is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnSave() error {
|
||||
msg := fmt.Sprintf("received on save trigger")
|
||||
n.AddQueue("onsave", msg)
|
||||
return errors.New("onsave triggered")
|
||||
}
|
||||
|
||||
// OnSuccess is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnSuccess(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a count trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnFailure is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf("received a failure trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnTest is a option testing event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnTest() error {
|
||||
fmt.Printf("received a test trigger with form data: %v\n", n.Host)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnNewService is a option event for new services
|
||||
func (n *ExampleNotifier) OnNewService(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a new service trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedService is a option event for updated services
|
||||
func (n *ExampleNotifier) OnUpdatedService(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a update service trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnDeletedService is a option event for deleted services
|
||||
func (n *ExampleNotifier) OnDeletedService(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a delete service trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnNewUser is a option event for new users
|
||||
func (n *ExampleNotifier) OnNewUser(s *types.User) {
|
||||
msg := fmt.Sprintf("received a new user trigger for user: %v\n", s.Username)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedUser is a option event for updated users
|
||||
func (n *ExampleNotifier) OnUpdatedUser(s *types.User) {
|
||||
msg := fmt.Sprintf("received a updated user trigger for user: %v\n", s.Username)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnDeletedUser is a option event for deleted users
|
||||
func (n *ExampleNotifier) OnDeletedUser(s *types.User) {
|
||||
msg := fmt.Sprintf("received a deleted user trigger for user: %v\n", s.Username)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedCore is a option event when the settings are updated
|
||||
func (n *ExampleNotifier) OnUpdatedCore(s *types.Core) {
|
||||
msg := fmt.Sprintf("received a updated core trigger for core: %v\n", s.Name)
|
||||
n.AddQueue("core", msg)
|
||||
}
|
||||
|
||||
// OnStart is triggered when statup has been started
|
||||
func (n *ExampleNotifier) OnStart(s *types.Core) {
|
||||
msg := fmt.Sprintf("received a trigger on Statping boot: %v\n", s.Name)
|
||||
n.AddQueue(fmt.Sprintf("core"), msg)
|
||||
}
|
||||
|
||||
// OnNewNotifier is triggered when a new notifier has initialized
|
||||
func (n *ExampleNotifier) OnNewNotifier(s *Notification) {
|
||||
msg := fmt.Sprintf("received a new notifier trigger for notifier: %v\n", s.Method)
|
||||
n.AddQueue(fmt.Sprintf("notifier_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedNotifier is triggered when a notifier has been updated
|
||||
func (n *ExampleNotifier) OnUpdatedNotifier(s *Notification) {
|
||||
msg := fmt.Sprintf("received a update notifier trigger for notifier: %v\n", s.Method)
|
||||
n.AddQueue(fmt.Sprintf("notifier_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// Create a new notifier that includes a form for the end user to insert their own values
|
||||
func ExampleNotification() {
|
||||
// Create a new variable for your Notifier
|
||||
example = &ExampleNotifier{&Notification{
|
||||
Method: "Example",
|
||||
Title: "Example Notifier",
|
||||
Description: "Example Notifier can hold many different types of fields for a customized look.",
|
||||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
Delay: time.Duration(1500 * time.Millisecond),
|
||||
Limits: 7,
|
||||
Form: []NotificationForm{{
|
||||
Type: "text",
|
||||
Title: "Host",
|
||||
Placeholder: "Insert your Host here.",
|
||||
DbField: "host",
|
||||
SmallText: "you can also use SmallText to insert some helpful hints under this input",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "API Key",
|
||||
Placeholder: "Include some type of API key here",
|
||||
DbField: "api_key",
|
||||
}},
|
||||
}}
|
||||
|
||||
// AddNotifier accepts a Notifier to load into the Statping Notification system
|
||||
err := AddNotifiers(example)
|
||||
fmt.Println(err)
|
||||
// Output: <nil>
|
||||
}
|
||||
|
||||
// Add a Notifier to the AddQueue function to insert it into the system
|
||||
func ExampleAddNotifier() {
|
||||
err := AddNotifiers(example)
|
||||
fmt.Println(err)
|
||||
// Output: <nil>
|
||||
}
|
||||
|
||||
// OnSuccess will be triggered everytime a service is online
|
||||
func ExampleNotification_OnSuccess() {
|
||||
msg := fmt.Sprintf("this is a successful message as a string passing into AddQueue function")
|
||||
example.AddQueue("example", msg)
|
||||
fmt.Println(len(example.Queue))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
// Add a new message into the queue OnSuccess
|
||||
func ExampleOnSuccess() {
|
||||
msg := fmt.Sprintf("received a count trigger for service: %v\n", service.Name)
|
||||
example.AddQueue("example", msg)
|
||||
}
|
||||
|
||||
// Add a new message into the queue OnFailure
|
||||
func ExampleOnFailure() {
|
||||
msg := fmt.Sprintf("received a failing service: %v\n", service.Name)
|
||||
example.AddQueue("example", msg)
|
||||
}
|
||||
|
||||
// OnTest allows your notifier to be testable
|
||||
func ExampleOnTest() {
|
||||
err := example.OnTest()
|
||||
fmt.Print(err)
|
||||
// Output <nil>
|
||||
}
|
||||
|
||||
// Implement the Test interface to give your notifier testing abilities
|
||||
func ExampleNotification_CanTest() {
|
||||
testable := example.CanTest()
|
||||
fmt.Print(testable)
|
||||
// Output: true
|
||||
}
|
||||
|
||||
// Add any type of interface to the AddQueue function to be ran in the queue
|
||||
func ExampleNotification_AddQueue() {
|
||||
msg := fmt.Sprintf("this is a failing message as a string passing into AddQueue function")
|
||||
example.AddQueue("example", msg)
|
||||
queue := example.Queue
|
||||
fmt.Printf("Example has %v items in the queue", len(queue))
|
||||
// Output:
|
||||
// Example has 2 items in the queue
|
||||
}
|
||||
|
||||
// The Send method will run the main functionality of your notifier
|
||||
func ExampleNotification_Send() {
|
||||
msg := "this can be any type of interface"
|
||||
example.Send(msg)
|
||||
queue := example.Queue
|
||||
fmt.Printf("Example has %v items in the queue", len(queue))
|
||||
// Output:
|
||||
// i received this string: this can be any type of interface
|
||||
// Example has 2 items in the queue
|
||||
}
|
||||
|
||||
// LastSent will return the time.Duration of the last sent message
|
||||
func ExampleNotification_LastSent() {
|
||||
last := example.LastSent()
|
||||
fmt.Printf("Last message was sent %v seconds ago", last.Seconds())
|
||||
// Output: Last message was sent 0 seconds ago
|
||||
}
|
||||
|
||||
// Logs will return a slice of previously sent items from your notifier
|
||||
func ExampleNotification_Logs() {
|
||||
logs := example.Logs()
|
||||
fmt.Printf("Example has %v items in the log", len(logs))
|
||||
// Output: Example has 0 items in the log
|
||||
}
|
||||
|
||||
// SentLastMinute will return he amount of notifications sent in last 1 minute
|
||||
func ExampleNotification_SentLastMinute() {
|
||||
lastMinute := example.SentLastMinute()
|
||||
fmt.Printf("%v notifications sent in the last minute", lastMinute)
|
||||
// Output: 0 notifications sent in the last minute
|
||||
}
|
||||
|
||||
// SentLastHour will return he amount of notifications sent in last 1 hour
|
||||
func ExampleNotification_SentLastHour() {
|
||||
lastHour := example.SentLastHour()
|
||||
fmt.Printf("%v notifications sent in the last hour", lastHour)
|
||||
// Output: 0 notifications sent in the last hour
|
||||
}
|
||||
|
||||
// SentLastHour will return he amount of notifications sent in last 1 hour
|
||||
func ExampleNotification_WithinLimits() {
|
||||
ok, err := example.WithinLimits()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if ok {
|
||||
fmt.Printf("Example notifier is still within its sending limits")
|
||||
}
|
||||
// Output: Example notifier is still within its sending limits
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package notifier
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
|
||||
// Notifier interface is required to create a new Notifier
|
||||
type Notifier interface {
|
||||
OnSave() error // OnSave is triggered when the notifier is saved
|
||||
Send(interface{}) error // OnSave is triggered when the notifier is saved
|
||||
Select() *Notification // Select returns the *Notification for a notifier
|
||||
}
|
||||
|
||||
// BasicEvents includes the most minimal events, failing and successful service triggers
|
||||
type BasicEvents interface {
|
||||
OnSuccess(*types.Service) // OnSuccess is triggered when a service is successful
|
||||
OnFailure(*types.Service, *types.Failure) // OnFailure is triggered when a service is failing
|
||||
}
|
||||
|
||||
// Tester interface will include a function to Test users settings before saving
|
||||
type Tester interface {
|
||||
OnTest() error
|
||||
}
|
||||
|
||||
// ServiceEvents are events for Services
|
||||
type ServiceEvents interface {
|
||||
OnNewService(*types.Service)
|
||||
OnUpdatedService(*types.Service)
|
||||
OnDeletedService(*types.Service)
|
||||
}
|
||||
|
||||
// UserEvents are events for Users
|
||||
type UserEvents interface {
|
||||
OnNewUser(*types.User)
|
||||
OnUpdatedUser(*types.User)
|
||||
OnDeletedUser(*types.User)
|
||||
}
|
||||
|
||||
// CoreEvents are events for the main Core app
|
||||
type CoreEvents interface {
|
||||
OnUpdatedCore(*types.Core)
|
||||
OnStart(*types.Core)
|
||||
}
|
||||
|
||||
// NotifierEvents are events for other Notifiers
|
||||
type NotifierEvents interface {
|
||||
OnNewNotifier(*Notification)
|
||||
OnUpdatedNotifier(*Notification)
|
||||
}
|
||||
|
||||
// HTTPRouter interface will allow your notifier to accept http GET/POST requests
|
||||
type HTTPRouter interface {
|
||||
OnGET() error
|
||||
OnPOST() error
|
||||
}
|
|
@ -1,483 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/jinzhu/gorm"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// AllCommunications holds all the loaded notifiers
|
||||
AllCommunications []types.AllNotifiers
|
||||
// db holds the Statping database connection
|
||||
db *gorm.DB
|
||||
timezone float32
|
||||
log = utils.Log.WithField("type", "notifier")
|
||||
)
|
||||
|
||||
// Notification contains all the fields for a Statping Notifier.
|
||||
type Notification struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
Method string `gorm:"column:method" json:"method"`
|
||||
Host string `gorm:"not null;column:host" json:"host,omitempty"`
|
||||
Port int `gorm:"not null;column:port" json:"port,omitempty"`
|
||||
Username string `gorm:"not null;column:username" json:"username,omitempty"`
|
||||
Password string `gorm:"not null;column:password" json:"password,omitempty"`
|
||||
Var1 string `gorm:"not null;column:var1" json:"var1,omitempty"`
|
||||
Var2 string `gorm:"not null;column:var2" json:"var2,omitempty"`
|
||||
ApiKey string `gorm:"not null;column:api_key" json:"api_key,omitempty"`
|
||||
ApiSecret string `gorm:"not null;column:api_secret" json:"api_secret,omitempty"`
|
||||
Enabled types.NullBool `gorm:"column:enabled;type:boolean;default:false" json:"enabled"`
|
||||
Limits int `gorm:"not null;column:limits" json:"limits"`
|
||||
Removable bool `gorm:"column:removable" json:"removeable"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
Form []NotificationForm `gorm:"-" json:"form"`
|
||||
logs []*NotificationLog `gorm:"-" json:"logs"`
|
||||
Title string `gorm:"-" json:"title"`
|
||||
Description string `gorm:"-" json:"description"`
|
||||
Author string `gorm:"-" json:"author"`
|
||||
AuthorUrl string `gorm:"-" json:"author_url"`
|
||||
Icon string `gorm:"-" json:"icon"`
|
||||
Delay time.Duration `gorm:"-" json:"delay,string"`
|
||||
Queue []*QueueData `gorm:"-" json:"-"`
|
||||
Running chan bool `gorm:"-" json:"-"`
|
||||
testable bool `gorm:"-" json:"testable"`
|
||||
}
|
||||
|
||||
// QueueData is the struct for the messaging queue with service
|
||||
type QueueData struct {
|
||||
Id string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.
|
||||
type NotificationForm struct {
|
||||
Type string `json:"type"` // the html input type (text, password, email)
|
||||
Title string `json:"title"` // include a title for ease of use
|
||||
Placeholder string `json:"placeholder"` // add a placeholder for the input
|
||||
DbField string `json:"field"` // true variable key for input
|
||||
SmallText string `json:"small_text"` // insert small text under a html input
|
||||
Required bool `json:"required"` // require this input on the html form
|
||||
IsHidden bool `json:"hidden"` // hide this form element from end user
|
||||
IsList bool `json:"list"` // make this form element a comma separated list
|
||||
IsSwitch bool `json:"switch"` // make the notifier a boolean true/false switch
|
||||
}
|
||||
|
||||
// NotificationLog contains the normalized message from previously sent notifications
|
||||
type NotificationLog struct {
|
||||
Message string `json:"message"`
|
||||
Time utils.Timestamp `json:"time"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// AfterFind for Notification will set the timezone
|
||||
func (n *Notification) AfterFind() (err error) {
|
||||
n.CreatedAt = utils.Timezoner(n.CreatedAt, timezone)
|
||||
n.UpdatedAt = utils.Timezoner(n.UpdatedAt, timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AddQueue will add any type of interface (json, string, struct, etc) into the Notifiers queue
|
||||
func (n *Notification) AddQueue(uid string, msg interface{}) {
|
||||
data := &QueueData{uid, msg}
|
||||
n.Queue = append(n.Queue, data)
|
||||
log.WithFields(utils.ToFields(data, n)).Infoln(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue)))
|
||||
}
|
||||
|
||||
// CanTest returns true if the notifier implements the OnTest interface
|
||||
func (n *Notification) CanTest() bool {
|
||||
return n.testable
|
||||
}
|
||||
|
||||
// db will return the notifier database column/record
|
||||
func modelDb(n *Notification) *gorm.DB {
|
||||
return db.Model(&Notification{}).Where("method = ?", n.Method).Find(n)
|
||||
}
|
||||
|
||||
// SetDB is called by core to inject the database for a notifier to use
|
||||
func SetDB(d *gorm.DB, zone float32) {
|
||||
db = d
|
||||
timezone = zone
|
||||
}
|
||||
|
||||
// asNotification accepts a Notifier and returns a Notification struct
|
||||
func asNotification(n Notifier) *Notification {
|
||||
return n.Select()
|
||||
}
|
||||
|
||||
// AddNotifier accept a Notifier interface to be added into the array
|
||||
func AddNotifiers(notifiers ...Notifier) error {
|
||||
for _, n := range notifiers {
|
||||
if isType(n, new(Notifier)) {
|
||||
err := checkNotifierForm(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
AllCommunications = append(AllCommunications, n)
|
||||
Init(n)
|
||||
} else {
|
||||
return errors.New("notifier does not have the required methods")
|
||||
}
|
||||
}
|
||||
startAllNotifiers()
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeType will accept multiple interfaces and converts it into a string for logging
|
||||
func normalizeType(ty interface{}) string {
|
||||
switch v := ty.(type) {
|
||||
case int, int32, int64:
|
||||
return fmt.Sprintf("%v", v)
|
||||
case float32, float64:
|
||||
return fmt.Sprintf("%v", v)
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
case []string:
|
||||
return fmt.Sprintf("%v", v)
|
||||
case interface{}, map[string]interface{}:
|
||||
j, _ := json.Marshal(v)
|
||||
return string(j)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Log will record a new notification into memory and will show the logs on the settings page
|
||||
func (n *Notification) makeLog(msg interface{}) {
|
||||
log := &NotificationLog{
|
||||
Message: normalizeType(msg),
|
||||
Time: utils.Timestamp(utils.Now()),
|
||||
Timestamp: utils.Now(),
|
||||
}
|
||||
n.logs = append(n.logs, log)
|
||||
}
|
||||
|
||||
// Logs returns an array of the notifiers logs
|
||||
func (n *Notification) Logs() []*NotificationLog {
|
||||
return reverseLogs(n.logs)
|
||||
}
|
||||
|
||||
// reverseLogs will reverse the notifier's logs to be time desc
|
||||
func reverseLogs(input []*NotificationLog) []*NotificationLog {
|
||||
if len(input) == 0 {
|
||||
return input
|
||||
}
|
||||
return append(reverseLogs(input[1:]), input[0])
|
||||
}
|
||||
|
||||
// isInDatabase returns true if the notifier has already been installed
|
||||
func isInDatabase(n Notifier) bool {
|
||||
inDb := modelDb(n.Select()).RecordNotFound()
|
||||
return !inDb
|
||||
}
|
||||
|
||||
// SelectNotification returns the Notification struct from the database
|
||||
func SelectNotification(n Notifier) (*Notification, error) {
|
||||
notifier := n.Select()
|
||||
err := db.Model(&Notification{}).Where("method = ?", notifier.Method).Scan(¬ifier)
|
||||
return notifier, err.Error
|
||||
}
|
||||
|
||||
// Update will update the notification into the database
|
||||
func Update(n Notifier, notif *Notification) (*Notification, error) {
|
||||
notif.ResetQueue()
|
||||
err := db.Model(&Notification{}).Update(notif)
|
||||
if notif.Enabled.Bool {
|
||||
notif.close()
|
||||
notif.start()
|
||||
go Queue(n)
|
||||
} else {
|
||||
notif.close()
|
||||
}
|
||||
return notif, err.Error
|
||||
}
|
||||
|
||||
// insertDatabase will create a new record into the database for the notifier
|
||||
func insertDatabase(n Notifier) (int64, error) {
|
||||
noti := n.Select()
|
||||
noti.Limits = 3
|
||||
query := db.Create(noti)
|
||||
if query.Error != nil {
|
||||
return 0, query.Error
|
||||
}
|
||||
return noti.Id, query.Error
|
||||
}
|
||||
|
||||
// SelectNotifier returns the Notification struct from the database
|
||||
func SelectNotifier(method string) (*Notification, Notifier, error) {
|
||||
for _, comm := range AllCommunications {
|
||||
n, ok := comm.(Notifier)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("incorrect notification type: %v", reflect.TypeOf(n).String())
|
||||
}
|
||||
notifier := n.Select()
|
||||
if notifier.Method == method {
|
||||
return notifier, comm.(Notifier), nil
|
||||
}
|
||||
}
|
||||
return nil, nil, errors.New("cannot find notifier")
|
||||
}
|
||||
|
||||
// Init accepts the Notifier interface to initialize the notifier
|
||||
func Init(n Notifier) (*Notification, error) {
|
||||
err := install(n)
|
||||
var notify *Notification
|
||||
if err == nil {
|
||||
notify, _ = SelectNotification(n)
|
||||
notify.CreatedAt = utils.Timezoner(notify.CreatedAt, timezone)
|
||||
notify.UpdatedAt = utils.Timezoner(notify.UpdatedAt, timezone)
|
||||
if notify.Delay.Seconds() == 0 {
|
||||
notify.Delay = time.Duration(1 * time.Second)
|
||||
}
|
||||
notify.testable = isType(n, new(Tester))
|
||||
notify.Form = n.Select().Form
|
||||
}
|
||||
return notify, err
|
||||
}
|
||||
|
||||
// startAllNotifiers will start the go routine for each loaded notifier
|
||||
func startAllNotifiers() {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(Notifier)) {
|
||||
notify := comm.(Notifier)
|
||||
if notify.Select().Enabled.Bool {
|
||||
notify.Select().close()
|
||||
notify.Select().start()
|
||||
go Queue(notify)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Queue is the FIFO go routine to send notifications when objects are triggered
|
||||
func Queue(n Notifier) {
|
||||
notification := n.Select()
|
||||
rateLimit := notification.Delay
|
||||
|
||||
CheckNotifier:
|
||||
for {
|
||||
select {
|
||||
case <-notification.Running:
|
||||
break CheckNotifier
|
||||
case <-time.After(rateLimit):
|
||||
notification = n.Select()
|
||||
if len(notification.Queue) > 0 {
|
||||
ok, _ := notification.WithinLimits()
|
||||
if ok {
|
||||
msg := notification.Queue[0]
|
||||
err := n.Send(msg.Data)
|
||||
if err != nil {
|
||||
log.WithFields(utils.ToFields(notification, msg)).Warnln(fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err))
|
||||
} else {
|
||||
log.WithFields(utils.ToFields(notification, msg)).Infoln(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue)))
|
||||
}
|
||||
notification.makeLog(msg.Data)
|
||||
if len(notification.Queue) > 1 {
|
||||
notification.Queue = notification.Queue[1:]
|
||||
} else {
|
||||
notification.Queue = nil
|
||||
}
|
||||
rateLimit = notification.Delay
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// install will check the database for the notification, if its not inserted it will insert a new record for it
|
||||
func install(n Notifier) error {
|
||||
inDb := isInDatabase(n)
|
||||
log.WithField("installed", inDb).
|
||||
WithFields(utils.ToFields(n)).
|
||||
Debugln(fmt.Sprintf("Checking if notifier '%v' is installed: %v", n.Select().Method, inDb))
|
||||
if !inDb {
|
||||
_, err := insertDatabase(n)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastSent returns a time.Duration of the last sent notification for the notifier
|
||||
func (n *Notification) LastSent() time.Duration {
|
||||
if len(n.logs) == 0 {
|
||||
return time.Duration(0)
|
||||
}
|
||||
last := n.Logs()[0]
|
||||
since := time.Since(last.Timestamp)
|
||||
return since
|
||||
}
|
||||
|
||||
// SentLastHour returns the total amount of notifications sent in last 1 hour
|
||||
func (n *Notification) SentLastHour() int {
|
||||
since := utils.Now().Add(-1 * time.Hour)
|
||||
return n.SentLast(since)
|
||||
}
|
||||
|
||||
// SentLastMinute returns the total amount of notifications sent in last 1 minute
|
||||
func (n *Notification) SentLastMinute() int {
|
||||
since := utils.Now().Add(-1 * time.Minute)
|
||||
return n.SentLast(since)
|
||||
}
|
||||
|
||||
// SentLast accept a time.Time and returns the amount of sent notifications within your time to current
|
||||
func (n *Notification) SentLast(since time.Time) int {
|
||||
sent := 0
|
||||
for _, v := range n.Logs() {
|
||||
lastTime := time.Time(v.Time)
|
||||
if lastTime.After(since) {
|
||||
sent++
|
||||
}
|
||||
}
|
||||
return sent
|
||||
}
|
||||
|
||||
// GetValue returns the database value of a accept DbField value.
|
||||
func (n *Notification) GetValue(dbField string) string {
|
||||
dbField = strings.ToLower(dbField)
|
||||
switch dbField {
|
||||
case "host":
|
||||
return n.Host
|
||||
case "port":
|
||||
return fmt.Sprintf("%v", n.Port)
|
||||
case "username":
|
||||
return n.Username
|
||||
case "password":
|
||||
if n.Password != "" {
|
||||
return "##########"
|
||||
}
|
||||
case "var1":
|
||||
return n.Var1
|
||||
case "var2":
|
||||
return n.Var2
|
||||
case "api_key":
|
||||
return n.ApiKey
|
||||
case "api_secret":
|
||||
return n.ApiSecret
|
||||
case "limits":
|
||||
return utils.ToString(int(n.Limits))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isType will return true if a variable can implement an interface
|
||||
func isType(n interface{}, obj interface{}) bool {
|
||||
one := reflect.TypeOf(n)
|
||||
two := reflect.ValueOf(obj).Elem()
|
||||
return one.Implements(two.Type())
|
||||
}
|
||||
|
||||
// isEnabled returns true if the notifier is enabled
|
||||
func isEnabled(n interface{}) bool {
|
||||
notifier := n.(Notifier).Select()
|
||||
return notifier.Enabled.Bool
|
||||
}
|
||||
|
||||
// inLimits will return true if the notifier is within sending limits
|
||||
func inLimits(n interface{}) bool {
|
||||
notifier := n.(Notifier).Select()
|
||||
ok, _ := notifier.WithinLimits()
|
||||
return ok
|
||||
}
|
||||
|
||||
// WithinLimits returns true if the notifier is within its sending limits
|
||||
func (n *Notification) WithinLimits() (bool, error) {
|
||||
if n.SentLastMinute() == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if n.SentLastMinute() >= n.Limits {
|
||||
return false, fmt.Errorf("notifier sent %v out of %v in last minute", n.SentLastMinute(), n.Limits)
|
||||
}
|
||||
if n.LastSent().Seconds() == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if n.Delay.Seconds() >= n.LastSent().Seconds() {
|
||||
return false, fmt.Errorf("notifiers delay (%v) is greater than last message sent (%v)", n.Delay.Seconds(), n.LastSent().Seconds())
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ResetQueue will clear the notifiers Queue
|
||||
func (n *Notification) ResetQueue() {
|
||||
n.Queue = nil
|
||||
}
|
||||
|
||||
// ResetQueue will clear the notifiers Queue for a service
|
||||
func (n *Notification) ResetUniqueQueue(uid string) []*QueueData {
|
||||
var queue []*QueueData
|
||||
for _, v := range n.Queue {
|
||||
if v.Id != uid {
|
||||
queue = append(queue, v)
|
||||
}
|
||||
}
|
||||
n.Queue = queue
|
||||
return queue
|
||||
}
|
||||
|
||||
// start will start the go routine for the notifier queue
|
||||
func (n *Notification) start() {
|
||||
n.Running = make(chan bool)
|
||||
}
|
||||
|
||||
// close will stop the go routine for queue
|
||||
func (n *Notification) close() {
|
||||
if n.IsRunning() {
|
||||
close(n.Running)
|
||||
}
|
||||
}
|
||||
|
||||
// IsRunning will return true if the notifier is currently running a queue
|
||||
func (n *Notification) IsRunning() bool {
|
||||
if n.Running == nil {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-n.Running:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ExampleService can be used for the OnTest() method for notifiers
|
||||
var ExampleService = &types.Service{
|
||||
Id: 1,
|
||||
Name: "Interpol - All The Rage Back Home",
|
||||
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
LastStatusCode: 404,
|
||||
Expected: types.NewNullString("test example"),
|
||||
LastResponse: "<html>this is an example response</html>",
|
||||
CreatedAt: utils.Now().Add(-24 * time.Hour),
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
dir string
|
||||
METHOD = "example"
|
||||
)
|
||||
|
||||
var service = &types.Service{
|
||||
Name: "Interpol - All The Rage Back Home",
|
||||
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
AllowNotifications: types.NewNullBool(true),
|
||||
}
|
||||
|
||||
var failure = &types.Failure{
|
||||
Issue: "testing",
|
||||
}
|
||||
|
||||
var user = &types.User{
|
||||
Username: "admin",
|
||||
Email: "info@email.com",
|
||||
}
|
||||
|
||||
var core = &types.Core{
|
||||
Name: "testing notifiers",
|
||||
}
|
||||
|
||||
func injectDatabase() {
|
||||
utils.DeleteFile(dir + "/notifier.db")
|
||||
db, _ = gorm.Open("sqlite3", dir+"/notifier.db")
|
||||
db.CreateTable(&Notification{})
|
||||
}
|
||||
|
||||
func TestIsBasicType(t *testing.T) {
|
||||
assert.True(t, isType(example, new(Notifier)))
|
||||
assert.True(t, isType(example, new(BasicEvents)))
|
||||
assert.True(t, isType(example, new(ServiceEvents)))
|
||||
assert.True(t, isType(example, new(UserEvents)))
|
||||
assert.True(t, isType(example, new(CoreEvents)))
|
||||
assert.True(t, isType(example, new(NotifierEvents)))
|
||||
assert.True(t, isType(example, new(Tester)))
|
||||
}
|
||||
|
||||
func TestIsInDatabase(t *testing.T) {
|
||||
in := isInDatabase(example)
|
||||
assert.True(t, in)
|
||||
}
|
||||
|
||||
func TestSelectNotification(t *testing.T) {
|
||||
notifier, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "example", notifier.Method)
|
||||
assert.False(t, notifier.Enabled.Bool)
|
||||
assert.False(t, notifier.IsRunning())
|
||||
}
|
||||
|
||||
func TestAddQueue(t *testing.T) {
|
||||
msg := "this is a test in the queue!"
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 1, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestNotification_Update(t *testing.T) {
|
||||
notifier, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
notifier.Host = "http://demo.statping.com/api"
|
||||
notifier.Port = 9090
|
||||
notifier.Username = "admin"
|
||||
notifier.Password = "password123"
|
||||
notifier.Var1 = "var1_is_here"
|
||||
notifier.Var2 = "var2_is_here"
|
||||
notifier.ApiKey = "USBdu82HDiiuw9327yGYDGw"
|
||||
notifier.ApiSecret = "PQopncow929hUIDHGwiud"
|
||||
notifier.Limits = 10
|
||||
_, err = Update(example, notifier)
|
||||
assert.Nil(t, err)
|
||||
|
||||
selected, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "http://demo.statping.com/api", selected.GetValue("host"))
|
||||
assert.Equal(t, "http://demo.statping.com/api", example.Notification.Host)
|
||||
assert.Equal(t, "http://demo.statping.com/api", example.Host)
|
||||
assert.Equal(t, "USBdu82HDiiuw9327yGYDGw", selected.GetValue("api_key"))
|
||||
assert.Equal(t, "USBdu82HDiiuw9327yGYDGw", example.ApiKey)
|
||||
assert.False(t, selected.Enabled.Bool)
|
||||
assert.False(t, selected.IsRunning())
|
||||
}
|
||||
|
||||
func TestEnableNotification(t *testing.T) {
|
||||
notifier, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
notifier.Enabled = types.NewNullBool(true)
|
||||
updated, err := Update(example, notifier)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, updated.Enabled.Bool)
|
||||
assert.True(t, updated.IsRunning())
|
||||
}
|
||||
|
||||
func TestIsEnabled(t *testing.T) {
|
||||
assert.True(t, isEnabled(example))
|
||||
}
|
||||
|
||||
func TestIsRunning(t *testing.T) {
|
||||
assert.True(t, example.IsRunning())
|
||||
}
|
||||
|
||||
func TestLastSent(t *testing.T) {
|
||||
notifier, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "0s", notifier.LastSent().String())
|
||||
}
|
||||
|
||||
func TestWithinLimits(t *testing.T) {
|
||||
notifier, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 10, notifier.Limits)
|
||||
assert.True(t, inLimits(example))
|
||||
}
|
||||
|
||||
func TestNotification_GetValue(t *testing.T) {
|
||||
notifier, err := SelectNotification(example)
|
||||
assert.Nil(t, err)
|
||||
val := notifier.GetValue("Host")
|
||||
assert.Equal(t, "http://demo.statping.com/api", val)
|
||||
}
|
||||
|
||||
func TestOnSave(t *testing.T) {
|
||||
err := example.OnSave()
|
||||
assert.Equal(t, "onsave triggered", err.Error())
|
||||
}
|
||||
|
||||
func TestOnSuccess(t *testing.T) {
|
||||
OnSuccess(service)
|
||||
assert.Equal(t, 2, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnFailure(t *testing.T) {
|
||||
OnFailure(service, failure)
|
||||
assert.Equal(t, 3, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnNewService(t *testing.T) {
|
||||
OnNewService(service)
|
||||
assert.Equal(t, 4, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnUpdatedService(t *testing.T) {
|
||||
OnUpdatedService(service)
|
||||
assert.Equal(t, 5, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnDeletedService(t *testing.T) {
|
||||
OnDeletedService(service)
|
||||
assert.Equal(t, 6, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnNewUser(t *testing.T) {
|
||||
OnNewUser(user)
|
||||
assert.Equal(t, 7, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnUpdatedUser(t *testing.T) {
|
||||
OnUpdatedUser(user)
|
||||
assert.Equal(t, 8, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnDeletedUser(t *testing.T) {
|
||||
OnDeletedUser(user)
|
||||
assert.Equal(t, 9, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnUpdatedCore(t *testing.T) {
|
||||
OnUpdatedCore(core)
|
||||
assert.Equal(t, 10, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestOnUpdatedNotifier(t *testing.T) {
|
||||
OnUpdatedNotifier(example.Select())
|
||||
assert.Equal(t, 11, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestRunAllQueueAndStop(t *testing.T) {
|
||||
assert.True(t, example.IsRunning())
|
||||
assert.Equal(t, 11, len(example.Queue))
|
||||
go Queue(example)
|
||||
time.Sleep(13 * time.Second)
|
||||
assert.NotZero(t, len(example.Queue))
|
||||
example.close()
|
||||
assert.False(t, example.IsRunning())
|
||||
assert.NotZero(t, len(example.Queue))
|
||||
}
|
596
core/sample.go
596
core/sample.go
|
@ -1,596 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sampleStart = time.Now().Add((-24 * 7) * time.Hour).UTC()
|
||||
SampleHits = 9900.
|
||||
)
|
||||
|
||||
// InsertSampleData will create the example/dummy services for a brand new Statping installation
|
||||
func InsertSampleData() error {
|
||||
log.Infoln("Inserting Sample Data...")
|
||||
|
||||
insertSampleGroups()
|
||||
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
|
||||
s1 := ReturnService(&types.Service{
|
||||
Name: "Google",
|
||||
Domain: "https://google.com",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 10,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 1,
|
||||
GroupId: 1,
|
||||
Permalink: types.NewNullString("google"),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s2 := ReturnService(&types.Service{
|
||||
Name: "Statping Github",
|
||||
Domain: "https://github.com/hunterlong/statping",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
Order: 2,
|
||||
Permalink: types.NewNullString("statping_github"),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s3 := ReturnService(&types.Service{
|
||||
Name: "JSON Users Test",
|
||||
Domain: "https://jsonplaceholder.typicode.com/users",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 60,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 30,
|
||||
Order: 3,
|
||||
Public: types.NewNullBool(true),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
GroupId: 2,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s4 := ReturnService(&types.Service{
|
||||
Name: "JSON API Tester",
|
||||
Domain: "https://jsonplaceholder.typicode.com/posts",
|
||||
ExpectedStatus: 201,
|
||||
Expected: types.NewNullString(`(title)": "((\\"|[statping])*)"`),
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "POST",
|
||||
PostData: types.NewNullString(`{ "title": "statping", "body": "bar", "userId": 19999 }`),
|
||||
Timeout: 30,
|
||||
Order: 4,
|
||||
Public: types.NewNullBool(true),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
GroupId: 2,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s5 := ReturnService(&types.Service{
|
||||
Name: "Google DNS",
|
||||
Domain: "8.8.8.8",
|
||||
Interval: 20,
|
||||
Type: "tcp",
|
||||
Port: 53,
|
||||
Timeout: 120,
|
||||
Order: 5,
|
||||
Public: types.NewNullBool(true),
|
||||
GroupId: 1,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s1.Create(false)
|
||||
s2.Create(false)
|
||||
s3.Create(false)
|
||||
s4.Create(false)
|
||||
s5.Create(false)
|
||||
|
||||
insertMessages()
|
||||
|
||||
insertSampleIncidents()
|
||||
|
||||
log.Infoln("Sample data has finished importing")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertSampleIncidents() error {
|
||||
incident1 := &Incident{&types.Incident{
|
||||
Title: "Github Downtime",
|
||||
Description: "This is an example of a incident for a service.",
|
||||
ServiceId: 2,
|
||||
}}
|
||||
_, err := incident1.Create()
|
||||
|
||||
incidentUpdate1 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github's page for Statping seems to be sending a 501 error.",
|
||||
Type: "Investigating",
|
||||
}}
|
||||
_, err = incidentUpdate1.Create()
|
||||
|
||||
incidentUpdate2 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Problem is continuing and we are looking at the issues.",
|
||||
Type: "Update",
|
||||
}}
|
||||
_, err = incidentUpdate2.Create()
|
||||
|
||||
incidentUpdate3 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
}}
|
||||
_, err = incidentUpdate3.Create()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func insertSampleGroups() error {
|
||||
group1 := &Group{&types.Group{
|
||||
Name: "Main Services",
|
||||
Public: types.NewNullBool(true),
|
||||
Order: 2,
|
||||
}}
|
||||
_, err := group1.Create()
|
||||
group2 := &Group{&types.Group{
|
||||
Name: "Linked Services",
|
||||
Public: types.NewNullBool(false),
|
||||
Order: 1,
|
||||
}}
|
||||
_, err = group2.Create()
|
||||
group3 := &Group{&types.Group{
|
||||
Name: "Empty Group",
|
||||
Public: types.NewNullBool(false),
|
||||
Order: 3,
|
||||
}}
|
||||
_, err = group3.Create()
|
||||
return err
|
||||
}
|
||||
|
||||
// insertSampleCheckins will create 2 checkins with 60 successful hits per Checkin
|
||||
func insertSampleCheckins() error {
|
||||
s1 := SelectService(1)
|
||||
checkin1 := ReturnCheckin(&types.Checkin{
|
||||
ServiceId: s1.Id,
|
||||
Interval: 300,
|
||||
GracePeriod: 300,
|
||||
})
|
||||
checkin1.Update()
|
||||
|
||||
s2 := SelectService(1)
|
||||
checkin2 := ReturnCheckin(&types.Checkin{
|
||||
ServiceId: s2.Id,
|
||||
Interval: 900,
|
||||
GracePeriod: 300,
|
||||
})
|
||||
checkin2.Update()
|
||||
|
||||
checkTime := time.Now().UTC().Add(-24 * time.Hour)
|
||||
for i := 0; i <= 60; i++ {
|
||||
checkHit := ReturnCheckinHit(&types.CheckinHit{
|
||||
Checkin: checkin1.Id,
|
||||
From: "192.168.0.1",
|
||||
CreatedAt: checkTime.UTC(),
|
||||
})
|
||||
checkHit.Create()
|
||||
checkTime = checkTime.Add(10 * time.Minute)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertSampleHits will create a couple new hits for the sample services
|
||||
func InsertSampleHits() error {
|
||||
tx := hitsDB().Begin()
|
||||
sg := new(sync.WaitGroup)
|
||||
for i := int64(1); i <= 5; i++ {
|
||||
sg.Add(1)
|
||||
service := SelectService(i)
|
||||
seed := time.Now().UnixNano()
|
||||
log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
|
||||
createdAt := sampleStart
|
||||
p := utils.NewPerlin(2., 2., 10, seed)
|
||||
go func() {
|
||||
defer sg.Done()
|
||||
for hi := 0.; hi <= float64(SampleHits); hi++ {
|
||||
latency := p.Noise1D(hi / 500)
|
||||
createdAt = createdAt.Add(60 * time.Second)
|
||||
hit := &types.Hit{
|
||||
Service: service.Id,
|
||||
CreatedAt: createdAt,
|
||||
Latency: latency,
|
||||
}
|
||||
tx = tx.Create(&hit)
|
||||
}
|
||||
}()
|
||||
}
|
||||
sg.Wait()
|
||||
err := tx.Commit().Error
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// insertSampleCore will create a new Core for the seed
|
||||
func insertSampleCore() error {
|
||||
core := &types.Core{
|
||||
Name: "Statping Sample Data",
|
||||
Description: "This data is only used to testing",
|
||||
ApiKey: "sample",
|
||||
ApiSecret: "samplesecret",
|
||||
Domain: "http://localhost:8080",
|
||||
Version: "test",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UseCdn: types.NewNullBool(false),
|
||||
}
|
||||
query := coreDB().Create(core)
|
||||
return query.Error
|
||||
}
|
||||
|
||||
// insertSampleUsers will create 2 admin users for a seed database
|
||||
func insertSampleUsers() error {
|
||||
u2 := ReturnUser(&types.User{
|
||||
Username: "testadmin",
|
||||
Password: "password123",
|
||||
Email: "info@betatude.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
|
||||
u3 := ReturnUser(&types.User{
|
||||
Username: "testadmin2",
|
||||
Password: "password123",
|
||||
Email: "info@adminhere.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
|
||||
_, err := u2.Create()
|
||||
_, err = u3.Create()
|
||||
return err
|
||||
}
|
||||
|
||||
func insertMessages() error {
|
||||
m1 := ReturnMessage(&types.Message{
|
||||
Title: "Routine Downtime",
|
||||
Description: "This is an example a upcoming message for a service!",
|
||||
ServiceId: 1,
|
||||
StartOn: time.Now().UTC().Add(15 * time.Minute),
|
||||
EndOn: time.Now().UTC().Add(2 * time.Hour),
|
||||
})
|
||||
if _, err := m1.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
m2 := ReturnMessage(&types.Message{
|
||||
Title: "Server Reboot",
|
||||
Description: "This is another example a upcoming message for a service!",
|
||||
ServiceId: 3,
|
||||
StartOn: time.Now().Add(15 * time.Minute),
|
||||
EndOn: time.Now().Add(2 * time.Hour),
|
||||
})
|
||||
if _, err := m2.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertLargeSampleData will create the example/dummy services for testing the Statping server
|
||||
func InsertLargeSampleData() error {
|
||||
if err := insertSampleCore(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := InsertSampleData(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertSampleUsers(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertSampleCheckins(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertMessages(); err != nil {
|
||||
return err
|
||||
}
|
||||
createdOn := time.Now().UTC().Add((-24 * 90) * time.Hour)
|
||||
s6 := ReturnService(&types.Service{
|
||||
Name: "JSON Lint",
|
||||
Domain: "https://jsonlint.com",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 15,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 6,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s7 := ReturnService(&types.Service{
|
||||
Name: "Demo Page",
|
||||
Domain: "https://demo.statping.com",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 15,
|
||||
Order: 7,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s8 := ReturnService(&types.Service{
|
||||
Name: "Golang",
|
||||
Domain: "https://golang.org",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 15,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 8,
|
||||
})
|
||||
|
||||
s9 := ReturnService(&types.Service{
|
||||
Name: "Santa Monica",
|
||||
Domain: "https://www.santamonica.com",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 15,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 9,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s10 := ReturnService(&types.Service{
|
||||
Name: "Oeschs Die Dritten",
|
||||
Domain: "https://www.oeschs-die-dritten.ch/en/",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 15,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 10,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s11 := ReturnService(&types.Service{
|
||||
Name: "XS Project - Bochka, Bass, Kolbaser",
|
||||
Domain: "https://www.youtube.com/watch?v=VLW1ieY4Izw",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 60,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
Order: 11,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s12 := ReturnService(&types.Service{
|
||||
Name: "Github",
|
||||
Domain: "https://github.com/hunterlong",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 60,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
Order: 12,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s13 := ReturnService(&types.Service{
|
||||
Name: "Failing URL",
|
||||
Domain: "http://thisdomainisfakeanditsgoingtofail.com",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 45,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 13,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s14 := ReturnService(&types.Service{
|
||||
Name: "Oesch's die Dritten - Die Jodelsprache",
|
||||
Domain: "https://www.youtube.com/watch?v=k3GTxRt4iao",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 60,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 12,
|
||||
Order: 14,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s15 := ReturnService(&types.Service{
|
||||
Name: "Gorm",
|
||||
Domain: "http://gorm.io/",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 12,
|
||||
Order: 15,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
||||
s6.Create(false)
|
||||
s7.Create(false)
|
||||
s8.Create(false)
|
||||
s9.Create(false)
|
||||
s10.Create(false)
|
||||
s11.Create(false)
|
||||
s12.Create(false)
|
||||
s13.Create(false)
|
||||
s14.Create(false)
|
||||
s15.Create(false)
|
||||
|
||||
var dayAgo = time.Now().UTC().Add((-24 * 90) * time.Hour)
|
||||
|
||||
insertHitRecords(dayAgo, 5450)
|
||||
|
||||
insertFailureRecords(dayAgo, 730)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// insertFailureRecords will create failures for 15 services from seed
|
||||
func insertFailureRecords(since time.Time, amount int64) {
|
||||
for i := int64(14); i <= 15; i++ {
|
||||
service := SelectService(i)
|
||||
log.Infoln(fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
|
||||
createdAt := since
|
||||
|
||||
for fi := int64(1); fi <= amount; fi++ {
|
||||
createdAt = createdAt.Add(2 * time.Minute)
|
||||
|
||||
failure := &types.Failure{
|
||||
Service: service.Id,
|
||||
Issue: "testing right here",
|
||||
CreatedAt: createdAt,
|
||||
}
|
||||
|
||||
service.CreateFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insertHitRecords will create successful Hit records for 15 services
|
||||
func insertHitRecords(since time.Time, amount int64) {
|
||||
for i := int64(1); i <= 15; i++ {
|
||||
service := SelectService(i)
|
||||
log.Infoln(fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
|
||||
createdAt := since
|
||||
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano())
|
||||
for hi := int64(1); hi <= amount; hi++ {
|
||||
latency := p.Noise1D(float64(hi / 10))
|
||||
createdAt = createdAt.Add(1 * time.Minute)
|
||||
hit := &types.Hit{
|
||||
Service: service.Id,
|
||||
CreatedAt: createdAt.UTC(),
|
||||
Latency: latency,
|
||||
}
|
||||
service.CreateHit(hit)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TmpRecords is used for testing Statping. It will create a SQLite database file
|
||||
// with sample data and store it in the /tmp folder to be used by the tests.
|
||||
func TmpRecords(dbFile string) error {
|
||||
var sqlFile = utils.Directory + "/" + dbFile
|
||||
utils.CreateDirectory(utils.Directory + "/tmp")
|
||||
var tmpSqlFile = utils.Directory + "/tmp/" + types.SqliteFilename
|
||||
SampleHits = 480
|
||||
|
||||
var err error
|
||||
CoreApp = NewCore()
|
||||
CoreApp.Name = "Tester"
|
||||
configs := &types.DbConfig{
|
||||
DbConn: "sqlite",
|
||||
Project: "Tester",
|
||||
Location: utils.Directory,
|
||||
SqlFile: sqlFile,
|
||||
}
|
||||
log.Infoln("saving config.yml in: " + utils.Directory)
|
||||
if configs, err = CoreApp.SaveConfig(configs); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading config.yml from: " + utils.Directory)
|
||||
if configs, err = LoadConfigFile(utils.Directory); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("connecting to database")
|
||||
|
||||
exists := utils.FileExists(tmpSqlFile)
|
||||
if exists {
|
||||
log.Infoln(tmpSqlFile + " was found, copying the temp database to " + sqlFile)
|
||||
if err := utils.DeleteFile(sqlFile); err != nil {
|
||||
log.Infoln(sqlFile + " was not found")
|
||||
}
|
||||
if err := utils.CopyFile(tmpSqlFile, sqlFile); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading config.yml from: " + utils.Directory)
|
||||
|
||||
if err := CoreApp.Connect(false, utils.Directory); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("selecting the Core variable")
|
||||
if _, err := SelectCore(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("inserting notifiers into database")
|
||||
if err := InsertNotifierDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading all services")
|
||||
if _, err := CoreApp.SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := AttachNotifiers(); err != nil {
|
||||
return err
|
||||
}
|
||||
CoreApp.Notifications = notifier.AllCommunications
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infoln(tmpSqlFile + " not found, creating a new database...")
|
||||
|
||||
if err := CoreApp.Connect(false, utils.Directory); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("creating database")
|
||||
if err := CoreApp.CreateDatabase(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("migrating database")
|
||||
if err := CoreApp.MigrateDatabase(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("insert large sample data into database")
|
||||
if err := InsertLargeSampleData(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("selecting the Core variable")
|
||||
if CoreApp, err = SelectCore(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("inserting notifiers into database")
|
||||
if err := InsertNotifierDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading all services")
|
||||
if _, err := CoreApp.SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("copying sql database file to: " + tmpSqlFile)
|
||||
if err := utils.CopyFile(sqlFile, tmpSqlFile); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
454
core/services.go
454
core/services.go
|
@ -1,454 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
*types.Service
|
||||
}
|
||||
|
||||
// Select will return the *types.Service struct for Service
|
||||
func (s *Service) Select() *types.Service {
|
||||
return s.Service
|
||||
}
|
||||
|
||||
// ReturnService will convert *types.Service to *core.Service
|
||||
func ReturnService(s *types.Service) *Service {
|
||||
return &Service{s}
|
||||
}
|
||||
|
||||
func Services() []types.ServiceInterface {
|
||||
return CoreApp.Services
|
||||
}
|
||||
|
||||
// SelectService returns a *core.Service from in memory
|
||||
func SelectService(id int64) *Service {
|
||||
for _, s := range Services() {
|
||||
if s.Select().Id == id {
|
||||
return s.(*Service)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SelectServices(auth bool) []*Service {
|
||||
var validServices []*Service
|
||||
for _, sr := range CoreApp.Services {
|
||||
s := sr.(*Service)
|
||||
if !s.Public.Bool {
|
||||
if auth {
|
||||
validServices = append(validServices, s)
|
||||
}
|
||||
} else {
|
||||
validServices = append(validServices, s)
|
||||
}
|
||||
}
|
||||
return validServices
|
||||
}
|
||||
|
||||
// SelectServiceLink returns a *core.Service from the service permalink
|
||||
func SelectServiceLink(permalink string) *Service {
|
||||
for _, s := range Services() {
|
||||
if s.Select().Permalink.String == permalink {
|
||||
return s.(*Service)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckinProcess runs the checkin routine for each checkin attached to service
|
||||
func (s *Service) CheckinProcess() {
|
||||
checkins := s.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Start()
|
||||
go c.Routine()
|
||||
}
|
||||
}
|
||||
|
||||
// AllCheckins will return a slice of AllCheckins for a Service
|
||||
func (s *Service) AllCheckins() []*Checkin {
|
||||
var checkin []*Checkin
|
||||
checkinDB().Where("service = ?", s.Id).Find(&checkin)
|
||||
return checkin
|
||||
}
|
||||
|
||||
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
|
||||
func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
|
||||
var services []*Service
|
||||
db := servicesDB().Find(&services).Order("order_id desc")
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("service error: %v", db.Error))
|
||||
return nil, db.Error
|
||||
}
|
||||
CoreApp.Services = nil
|
||||
for _, service := range services {
|
||||
if start {
|
||||
service.Start()
|
||||
service.CheckinProcess()
|
||||
}
|
||||
fails := service.LimitedFailures(limitedFailures)
|
||||
for _, f := range fails {
|
||||
service.Failures = append(service.Failures, f)
|
||||
}
|
||||
checkins := service.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Failures = c.LimitedFailures(limitedFailures)
|
||||
c.Hits = c.LimitedHits(limitedHits)
|
||||
service.Checkins = append(service.Checkins, c)
|
||||
}
|
||||
CoreApp.Services = append(CoreApp.Services, service)
|
||||
}
|
||||
reorderServices()
|
||||
return services, db.Error
|
||||
}
|
||||
|
||||
// reorderServices will sort the services based on 'order_id'
|
||||
func reorderServices() {
|
||||
sort.Sort(ServiceOrder(CoreApp.Services))
|
||||
}
|
||||
|
||||
// AvgTime will return the average amount of time for a service to response back successfully
|
||||
func (s *Service) AvgTime() string {
|
||||
total, _ := s.TotalHits()
|
||||
if total == 0 {
|
||||
return "0"
|
||||
}
|
||||
sum := s.Sum()
|
||||
avg := sum / float64(total) * 100
|
||||
return fmt.Sprintf("%0.0f", avg*10)
|
||||
}
|
||||
|
||||
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
|
||||
func (s *Service) OnlineDaysPercent(days int) float32 {
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
return s.OnlineSince(ago)
|
||||
}
|
||||
|
||||
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
|
||||
func (s *Service) OnlineSince(ago time.Time) float32 {
|
||||
failed, _ := s.TotalFailuresSince(ago)
|
||||
if failed == 0 {
|
||||
s.Online24Hours = 100.00
|
||||
return s.Online24Hours
|
||||
}
|
||||
total, _ := s.TotalHitsSince(ago)
|
||||
if total == 0 {
|
||||
s.Online24Hours = 0
|
||||
return s.Online24Hours
|
||||
}
|
||||
avg := float64(failed) / float64(total) * 100
|
||||
avg = 100 - avg
|
||||
if avg < 0 {
|
||||
avg = 0
|
||||
}
|
||||
amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10)
|
||||
s.Online24Hours = float32(amount)
|
||||
return s.Online24Hours
|
||||
}
|
||||
|
||||
// DateScan struct is for creating the charts.js graph JSON array
|
||||
type DateScan struct {
|
||||
CreatedAt string `json:"x,omitempty"`
|
||||
Value int64 `json:"y"`
|
||||
}
|
||||
|
||||
// DateScanObj struct is for creating the charts.js graph JSON array
|
||||
type DateScanObj struct {
|
||||
Array []DateScan `json:"data"`
|
||||
}
|
||||
|
||||
// lastFailure returns the last Failure a service had
|
||||
func (s *Service) lastFailure() *Failure {
|
||||
limited := s.LimitedFailures(1)
|
||||
if len(limited) == 0 {
|
||||
return nil
|
||||
}
|
||||
last := limited[len(limited)-1]
|
||||
return last
|
||||
}
|
||||
|
||||
// SmallText returns a short description about a services status
|
||||
// service.SmallText()
|
||||
// // Online since Monday 3:04:05PM, Jan _2 2006
|
||||
func (s *Service) SmallText() string {
|
||||
last := s.LimitedFailures(1)
|
||||
//hits, _ := s.LimitedHits(1)
|
||||
zone := CoreApp.Timezone
|
||||
if s.Online {
|
||||
if len(last) == 0 {
|
||||
return fmt.Sprintf("Online since %v", utils.Timezoner(s.CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
} else {
|
||||
return fmt.Sprintf("Online, last Failure was %v", utils.Timezoner(last[0].CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
}
|
||||
}
|
||||
if len(last) > 0 {
|
||||
lastFailure := s.lastFailure()
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().UTC().Add(s.Downtime()), time.Now().UTC())
|
||||
return fmt.Sprintf("Reported offline %v, %v", got, lastFailure.ParseError())
|
||||
} else {
|
||||
return fmt.Sprintf("%v is currently offline", s.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// DowntimeText will return the amount of downtime for a service based on the duration
|
||||
// service.DowntimeText()
|
||||
// // Service has been offline for 15 minutes
|
||||
func (s *Service) DowntimeText() string {
|
||||
return fmt.Sprintf("%v has been offline for %v", s.Name, utils.DurationReadable(s.Downtime()))
|
||||
}
|
||||
|
||||
// Dbtimestamp will return a SQL query for grouping by date
|
||||
func Dbtimestamp(group string, column string) string {
|
||||
seconds := 3600
|
||||
switch group {
|
||||
case "minute":
|
||||
seconds = 60
|
||||
case "hour":
|
||||
seconds = 3600
|
||||
case "day":
|
||||
seconds = 86400
|
||||
case "week":
|
||||
seconds = 604800
|
||||
case "month":
|
||||
seconds = 2592000
|
||||
case "year":
|
||||
seconds = 31557600
|
||||
default:
|
||||
seconds = 60
|
||||
}
|
||||
switch CoreApp.Config.DbConn {
|
||||
case "mysql":
|
||||
return fmt.Sprintf("CONCAT(date_format(created_at, '%%Y-%%m-%%d %%H:00:00')) AS timeframe, AVG(%v) AS value", column)
|
||||
case "postgres":
|
||||
return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(%v) AS value", group, column)
|
||||
default:
|
||||
return fmt.Sprintf("datetime((strftime('%%s', created_at) / %v) * %v, 'unixepoch') AS timeframe, AVG(%v) as value", seconds, seconds, column)
|
||||
}
|
||||
}
|
||||
|
||||
// Downtime returns the amount of time of a offline service
|
||||
func (s *Service) Downtime() time.Duration {
|
||||
hits, _ := s.Hits()
|
||||
fail := s.lastFailure()
|
||||
if fail == nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
if len(hits) == 0 {
|
||||
return time.Now().UTC().Sub(fail.CreatedAt.UTC())
|
||||
}
|
||||
since := fail.CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
|
||||
return since
|
||||
}
|
||||
|
||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||
func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group string, column string) *DateScanObj {
|
||||
var data []DateScan
|
||||
outgoing := new(DateScanObj)
|
||||
model := service.(*Service).HitsBetween(start, end, group, column)
|
||||
model = model.Order("timeframe asc", false).Group("timeframe")
|
||||
rows, err := model.Rows()
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Errorf("issue fetching service chart data: %v", err))
|
||||
}
|
||||
for rows.Next() {
|
||||
var gd DateScan
|
||||
var createdAt string
|
||||
var value float64
|
||||
var createdTime time.Time
|
||||
var err error
|
||||
rows.Scan(&createdAt, &value)
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
createdTime, err = time.Parse(types.TIME_NANO, createdAt)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
|
||||
}
|
||||
} else {
|
||||
createdTime, err = time.Parse(types.TIME, createdAt)
|
||||
}
|
||||
gd.CreatedAt = utils.Timezoner(createdTime, CoreApp.Timezone).Format(types.CHART_TIME)
|
||||
gd.Value = int64(value * 1000)
|
||||
data = append(data, gd)
|
||||
}
|
||||
outgoing.Array = data
|
||||
return outgoing
|
||||
}
|
||||
|
||||
// ToString will convert the DateScanObj into a JSON string for the charts to render
|
||||
func (d *DateScanObj) ToString() string {
|
||||
data, err := json.Marshal(d.Array)
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// AvgUptime24 returns a service's average online status for last 24 hours
|
||||
func (s *Service) AvgUptime24() string {
|
||||
ago := time.Now().UTC().Add(-24 * time.Hour)
|
||||
return s.AvgUptime(ago)
|
||||
}
|
||||
|
||||
// AvgUptime returns average online status for last 24 hours
|
||||
func (s *Service) AvgUptime(ago time.Time) string {
|
||||
failed, _ := s.TotalFailuresSince(ago)
|
||||
if failed == 0 {
|
||||
return "100"
|
||||
}
|
||||
total, _ := s.TotalHitsSince(ago)
|
||||
if total == 0 {
|
||||
return "0.00"
|
||||
}
|
||||
percent := float64(failed) / float64(total) * 100
|
||||
percent = 100 - percent
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
}
|
||||
amount := fmt.Sprintf("%0.2f", percent)
|
||||
if amount == "100.00" {
|
||||
amount = "100"
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
// TotalUptime returns the total uptime percent of a service
|
||||
func (s *Service) TotalUptime() string {
|
||||
hits, _ := s.TotalHits()
|
||||
failures, _ := s.TotalFailures()
|
||||
percent := float64(failures) / float64(hits) * 100
|
||||
percent = 100 - percent
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
}
|
||||
amount := fmt.Sprintf("%0.2f", percent)
|
||||
if amount == "100.00" {
|
||||
amount = "100"
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
// index returns a services index int for updating the []*core.Services slice
|
||||
func (s *Service) index() int {
|
||||
for k, service := range CoreApp.Services {
|
||||
if s.Id == service.(*Service).Id {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// updateService will update a service in the []*core.Services slice
|
||||
func updateService(s *Service) {
|
||||
CoreApp.Services[s.index()] = s
|
||||
}
|
||||
|
||||
// Delete will remove a service from the database, it will also end the service checking go routine
|
||||
func (s *Service) Delete() error {
|
||||
i := s.index()
|
||||
err := servicesDB().Delete(s)
|
||||
if err.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
|
||||
return err.Error
|
||||
}
|
||||
s.Close()
|
||||
slice := CoreApp.Services
|
||||
CoreApp.Services = append(slice[:i], slice[i+1:]...)
|
||||
reorderServices()
|
||||
notifier.OnDeletedService(s.Service)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Update will update a service in the database, the service's checking routine can be restarted by passing true
|
||||
func (s *Service) Update(restart bool) error {
|
||||
err := servicesDB().Update(&s)
|
||||
if err.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
|
||||
return err.Error
|
||||
}
|
||||
// clear the notification queue for a service
|
||||
if !s.AllowNotifications.Bool {
|
||||
for _, n := range CoreApp.Notifications {
|
||||
notif := n.(notifier.Notifier).Select()
|
||||
notif.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
}
|
||||
}
|
||||
if restart {
|
||||
s.Close()
|
||||
s.Start()
|
||||
s.SleepDuration = time.Duration(s.Interval) * time.Second
|
||||
go s.CheckQueue(true)
|
||||
}
|
||||
reorderServices()
|
||||
updateService(s)
|
||||
notifier.OnUpdatedService(s.Service)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Create will create a service and insert it into the database
|
||||
func (s *Service) Create(check bool) (int64, error) {
|
||||
s.CreatedAt = time.Now().UTC()
|
||||
db := servicesDB().Create(s)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
|
||||
return 0, db.Error
|
||||
}
|
||||
s.Start()
|
||||
go s.CheckQueue(check)
|
||||
CoreApp.Services = append(CoreApp.Services, s)
|
||||
reorderServices()
|
||||
notifier.OnNewService(s.Service)
|
||||
return s.Id, nil
|
||||
}
|
||||
|
||||
// Messages returns all Messages for a Service
|
||||
func (s *Service) Messages() []*Message {
|
||||
messages := SelectServiceMessages(s.Id)
|
||||
return messages
|
||||
}
|
||||
|
||||
// ActiveMessages returns all service messages that are available based on the current time
|
||||
func (s *Service) ActiveMessages() []*Message {
|
||||
var messages []*Message
|
||||
msgs := SelectServiceMessages(s.Id)
|
||||
for _, m := range msgs {
|
||||
if m.StartOn.UTC().After(time.Now().UTC()) {
|
||||
messages = append(messages, m)
|
||||
}
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
// CountOnline returns the amount of services online
|
||||
func (c *Core) CountOnline() int {
|
||||
amount := 0
|
||||
for _, s := range CoreApp.Services {
|
||||
if s.Select().Online {
|
||||
amount++
|
||||
}
|
||||
}
|
||||
return amount
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
testCheckin *Checkin
|
||||
)
|
||||
|
||||
func TestCreateCheckin(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
testCheckin = ReturnCheckin(&types.Checkin{
|
||||
ServiceId: service.Id,
|
||||
Interval: 10,
|
||||
GracePeriod: 5,
|
||||
ApiKey: utils.RandomString(7),
|
||||
})
|
||||
id, err := testCheckin.Create()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, id)
|
||||
assert.NotEmpty(t, testCheckin.ApiKey)
|
||||
assert.Equal(t, int64(10), testCheckin.Interval)
|
||||
assert.Equal(t, int64(5), testCheckin.GracePeriod)
|
||||
assert.True(t, testCheckin.Expected().Minutes() < 0)
|
||||
}
|
||||
|
||||
func TestSelectCheckin(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
checkins := service.AllCheckins()
|
||||
assert.NotNil(t, checkins)
|
||||
assert.Equal(t, 1, len(checkins))
|
||||
testCheckin = checkins[0]
|
||||
assert.Equal(t, int64(10), testCheckin.Interval)
|
||||
assert.Equal(t, int64(5), testCheckin.GracePeriod)
|
||||
assert.Equal(t, 7, len(testCheckin.ApiKey))
|
||||
}
|
||||
|
||||
func TestUpdateCheckin(t *testing.T) {
|
||||
testCheckin.Interval = 60
|
||||
testCheckin.GracePeriod = 15
|
||||
id, err := testCheckin.Update()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, id)
|
||||
assert.NotEmpty(t, testCheckin.ApiKey)
|
||||
service := SelectService(1)
|
||||
checkin := service.AllCheckins()[0]
|
||||
assert.Equal(t, int64(60), checkin.Interval)
|
||||
assert.Equal(t, int64(15), checkin.GracePeriod)
|
||||
t.Log(testCheckin.Expected())
|
||||
assert.True(t, testCheckin.Expected().Minutes() < 0)
|
||||
}
|
||||
|
||||
func TestCreateCheckinHits(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
checkins := service.AllCheckins()
|
||||
assert.Equal(t, 1, len(checkins))
|
||||
created := time.Now().UTC().Add(-60 * time.Second)
|
||||
hit := ReturnCheckinHit(&types.CheckinHit{
|
||||
Checkin: testCheckin.Id,
|
||||
From: "192.168.1.1",
|
||||
CreatedAt: created,
|
||||
})
|
||||
hit.Create()
|
||||
hits := testCheckin.AllHits()
|
||||
assert.Equal(t, 1, len(hits))
|
||||
}
|
||||
|
||||
func TestSelectCheckinMethods(t *testing.T) {
|
||||
time.Sleep(5 * time.Second)
|
||||
service := SelectService(1)
|
||||
checkins := service.AllCheckins()
|
||||
assert.NotNil(t, checkins)
|
||||
lastHit := testCheckin.Last()
|
||||
assert.Equal(t, float64(60), testCheckin.Period().Seconds())
|
||||
assert.Equal(t, float64(15), testCheckin.Grace().Seconds())
|
||||
t.Log(testCheckin.Expected())
|
||||
assert.True(t, testCheckin.Expected().Seconds() < -5)
|
||||
assert.False(t, lastHit.CreatedAt.IsZero())
|
||||
assert.Equal(t, "A minute ago", lastHit.Ago())
|
||||
}
|
|
@ -1,411 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
newServiceId int64
|
||||
)
|
||||
|
||||
func TestSelectHTTPService(t *testing.T) {
|
||||
services, err := CoreApp.SelectAllServices(false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 15, len(services))
|
||||
assert.Equal(t, "Google", services[0].Name)
|
||||
assert.Equal(t, "http", services[0].Type)
|
||||
}
|
||||
|
||||
func TestSelectAllServices(t *testing.T) {
|
||||
services := CoreApp.Services
|
||||
for _, s := range services {
|
||||
service := s.(*Service)
|
||||
service.Check(false)
|
||||
assert.False(t, service.IsRunning())
|
||||
t.Logf("ID: %v %v\n", service.Id, service.Name)
|
||||
}
|
||||
assert.Equal(t, 15, len(services))
|
||||
}
|
||||
|
||||
func TestServiceDowntime(t *testing.T) {
|
||||
t.SkipNow()
|
||||
service := SelectService(15)
|
||||
downtime := service.Downtime()
|
||||
assert.True(t, downtime.Seconds() > 0)
|
||||
}
|
||||
|
||||
func TestSelectTCPService(t *testing.T) {
|
||||
services := CoreApp.Services
|
||||
assert.Equal(t, 15, len(services))
|
||||
service := SelectService(5)
|
||||
assert.NotNil(t, service)
|
||||
assert.Equal(t, "Google DNS", service.Name)
|
||||
assert.Equal(t, "tcp", service.Type)
|
||||
}
|
||||
|
||||
func TestUpdateService(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
assert.Equal(t, "Google", service.Name)
|
||||
service.Name = "Updated Google"
|
||||
service.Interval = 5
|
||||
err := service.Update(true)
|
||||
assert.Nil(t, err)
|
||||
// check if updating pointer array shutdown any other service
|
||||
service = SelectService(1)
|
||||
assert.Equal(t, "Updated Google", service.Name)
|
||||
assert.Equal(t, 5, service.Interval)
|
||||
}
|
||||
|
||||
func TestUpdateAllServices(t *testing.T) {
|
||||
services, err := CoreApp.SelectAllServices(false)
|
||||
assert.Nil(t, err)
|
||||
for k, srv := range services {
|
||||
srv.Name = "Changed " + srv.Name
|
||||
srv.Interval = k + 3
|
||||
err := srv.Update(true)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceHTTPCheck(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
service.Check(true)
|
||||
assert.Equal(t, "Changed Updated Google", service.Name)
|
||||
assert.True(t, service.Online)
|
||||
}
|
||||
|
||||
func TestCheckHTTPService(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
assert.Equal(t, "Changed Updated Google", service.Name)
|
||||
assert.True(t, service.Online)
|
||||
assert.Equal(t, 200, service.LastStatusCode)
|
||||
assert.NotZero(t, service.Latency)
|
||||
assert.NotZero(t, service.PingTime)
|
||||
}
|
||||
|
||||
func TestServiceTCPCheck(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
service.Check(true)
|
||||
assert.Equal(t, "Changed Google DNS", service.Name)
|
||||
assert.True(t, service.Online)
|
||||
}
|
||||
|
||||
func TestCheckTCPService(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
assert.Equal(t, "Changed Google DNS", service.Name)
|
||||
assert.True(t, service.Online)
|
||||
assert.NotZero(t, service.Latency)
|
||||
assert.NotZero(t, service.PingTime)
|
||||
}
|
||||
|
||||
func TestServiceOnline24Hours(t *testing.T) {
|
||||
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
service := SelectService(1)
|
||||
assert.Equal(t, float32(100), service.OnlineSince(since))
|
||||
service2 := SelectService(5)
|
||||
assert.Equal(t, float32(100), service2.OnlineSince(since))
|
||||
service3 := SelectService(14)
|
||||
assert.True(t, service3.OnlineSince(since) > float32(49))
|
||||
}
|
||||
|
||||
func TestServiceSmallText(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
text := service.SmallText()
|
||||
assert.Contains(t, text, "Online since")
|
||||
}
|
||||
|
||||
func TestServiceAvgUptime(t *testing.T) {
|
||||
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
service := SelectService(1)
|
||||
assert.NotEqual(t, "0.00", service.AvgUptime(since))
|
||||
service2 := SelectService(5)
|
||||
assert.Equal(t, "100", service2.AvgUptime(since))
|
||||
service3 := SelectService(13)
|
||||
assert.NotEqual(t, "0", service3.AvgUptime(since))
|
||||
service4 := SelectService(15)
|
||||
assert.NotEqual(t, "0", service4.AvgUptime(since))
|
||||
}
|
||||
|
||||
func TestServiceHits(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
hits, err := service.Hits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(hits) > 1400)
|
||||
}
|
||||
|
||||
func TestServiceLimitedHits(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
hits, err := service.LimitedHits(1024)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int(1024), len(hits))
|
||||
}
|
||||
|
||||
func TestServiceTotalHits(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
hits, err := service.TotalHits()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, hits)
|
||||
}
|
||||
|
||||
func TestServiceSum(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
sum := service.Sum()
|
||||
assert.NotZero(t, sum)
|
||||
}
|
||||
|
||||
func TestCountOnline(t *testing.T) {
|
||||
amount := CoreApp.CountOnline()
|
||||
assert.True(t, amount >= 2)
|
||||
}
|
||||
|
||||
func TestCreateService(t *testing.T) {
|
||||
s := ReturnService(&types.Service{
|
||||
Name: "That'll do 🐢",
|
||||
Domain: "https://www.youtube.com/watch?v=rjQtzV9IZ0Q",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 3,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
GroupId: 1,
|
||||
})
|
||||
var err error
|
||||
newServiceId, err = s.Create(false)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, newServiceId)
|
||||
newService := SelectService(newServiceId)
|
||||
assert.Equal(t, "That'll do 🐢", newService.Name)
|
||||
}
|
||||
|
||||
func TestViewNewService(t *testing.T) {
|
||||
newService := SelectService(newServiceId)
|
||||
assert.Equal(t, "That'll do 🐢", newService.Name)
|
||||
}
|
||||
|
||||
func TestCreateFailingHTTPService(t *testing.T) {
|
||||
s := ReturnService(&types.Service{
|
||||
Name: "Bad URL",
|
||||
Domain: "http://localhost/iamnothere",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 2,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 5,
|
||||
GroupId: 1,
|
||||
})
|
||||
var err error
|
||||
newServiceId, err = s.Create(false)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, newServiceId)
|
||||
newService := SelectService(newServiceId)
|
||||
assert.Equal(t, "Bad URL", newService.Name)
|
||||
t.Log("new service ID: ", newServiceId)
|
||||
}
|
||||
|
||||
func TestServiceFailedCheck(t *testing.T) {
|
||||
service := SelectService(17)
|
||||
assert.Equal(t, "Bad URL", service.Name)
|
||||
service.Check(false)
|
||||
assert.Equal(t, "Bad URL", service.Name)
|
||||
assert.False(t, service.Online)
|
||||
}
|
||||
|
||||
func TestCreateFailingTCPService(t *testing.T) {
|
||||
s := ReturnService(&types.Service{
|
||||
Name: "Bad TCP",
|
||||
Domain: "localhost",
|
||||
Port: 5050,
|
||||
Interval: 30,
|
||||
Type: "tcp",
|
||||
Timeout: 5,
|
||||
GroupId: 1,
|
||||
})
|
||||
var err error
|
||||
newServiceId, err = s.Create(false)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, newServiceId)
|
||||
newService := SelectService(newServiceId)
|
||||
assert.Equal(t, "Bad TCP", newService.Name)
|
||||
t.Log("new failing tcp service ID: ", newServiceId)
|
||||
}
|
||||
|
||||
func TestServiceFailedTCPCheck(t *testing.T) {
|
||||
service := SelectService(newServiceId)
|
||||
service.Check(false)
|
||||
assert.Equal(t, "Bad TCP", service.Name)
|
||||
assert.False(t, service.Online)
|
||||
}
|
||||
|
||||
func TestCreateServiceFailure(t *testing.T) {
|
||||
fail := &types.Failure{
|
||||
Issue: "This is not an issue, but it would container HTTP response errors.",
|
||||
Method: "http",
|
||||
}
|
||||
service := SelectService(8)
|
||||
id, err := service.CreateFailure(fail)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, id)
|
||||
}
|
||||
|
||||
func TestDeleteService(t *testing.T) {
|
||||
service := SelectService(newServiceId)
|
||||
|
||||
count, err := CoreApp.SelectAllServices(false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 18, len(count))
|
||||
|
||||
err = service.Delete()
|
||||
assert.Nil(t, err)
|
||||
|
||||
services := CoreApp.Services
|
||||
assert.Equal(t, 17, len(services))
|
||||
}
|
||||
|
||||
func TestServiceCloseRoutine(t *testing.T) {
|
||||
s := ReturnService(new(types.Service))
|
||||
s.Name = "example"
|
||||
s.Domain = "https://google.com"
|
||||
s.Type = "http"
|
||||
s.Method = "GET"
|
||||
s.ExpectedStatus = 200
|
||||
s.Interval = 1
|
||||
s.Start()
|
||||
assert.True(t, s.IsRunning())
|
||||
t.Log(s.Checkpoint)
|
||||
t.Log(s.SleepDuration)
|
||||
go s.CheckQueue(false)
|
||||
t.Log(s.Checkpoint)
|
||||
t.Log(s.SleepDuration)
|
||||
time.Sleep(5 * time.Second)
|
||||
t.Log(s.Checkpoint)
|
||||
t.Log(s.SleepDuration)
|
||||
assert.True(t, s.IsRunning())
|
||||
s.Close()
|
||||
assert.False(t, s.IsRunning())
|
||||
s.Close()
|
||||
assert.False(t, s.IsRunning())
|
||||
}
|
||||
|
||||
func TestServiceCheckQueue(t *testing.T) {
|
||||
s := ReturnService(new(types.Service))
|
||||
s.Name = "example"
|
||||
s.Domain = "https://google.com"
|
||||
s.Type = "http"
|
||||
s.Method = "GET"
|
||||
s.ExpectedStatus = 200
|
||||
s.Interval = 1
|
||||
s.Start()
|
||||
assert.True(t, s.IsRunning())
|
||||
go s.CheckQueue(false)
|
||||
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
t.Log(s.Checkpoint)
|
||||
time.Sleep(6 * time.Second)
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
assert.True(t, s.IsRunning())
|
||||
s.Close()
|
||||
assert.False(t, s.IsRunning())
|
||||
s.Close()
|
||||
assert.False(t, s.IsRunning())
|
||||
}
|
||||
|
||||
func TestDNScheckService(t *testing.T) {
|
||||
s := ReturnService(new(types.Service))
|
||||
s.Name = "example"
|
||||
s.Domain = "http://localhost:9000"
|
||||
s.Type = "http"
|
||||
s.Method = "GET"
|
||||
s.ExpectedStatus = 200
|
||||
s.Interval = 1
|
||||
amount, err := s.dnsCheck()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, amount)
|
||||
}
|
||||
|
||||
func TestSelectServiceLink(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
assert.Equal(t, "google", service.Permalink.String)
|
||||
}
|
||||
|
||||
func TestDbtimestamp(t *testing.T) {
|
||||
CoreApp.Config.DbConn = "mysql"
|
||||
query := Dbtimestamp("minute", "latency")
|
||||
assert.Equal(t, "CONCAT(date_format(created_at, '%Y-%m-%d %H:00:00')) AS timeframe, AVG(latency) AS value", query)
|
||||
CoreApp.Config.DbConn = "postgres"
|
||||
query = Dbtimestamp("minute", "latency")
|
||||
assert.Equal(t, "date_trunc('minute', created_at) AS timeframe, AVG(latency) AS value", query)
|
||||
CoreApp.Config.DbConn = "sqlite"
|
||||
query = Dbtimestamp("minute", "latency")
|
||||
assert.Equal(t, "datetime((strftime('%s', created_at) / 60) * 60, 'unixepoch') AS timeframe, AVG(latency) as value", query)
|
||||
}
|
||||
|
||||
func TestGroup_Create(t *testing.T) {
|
||||
group := &Group{&types.Group{
|
||||
Name: "Testing",
|
||||
}}
|
||||
newGroupId, err := group.Create()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, newGroupId)
|
||||
}
|
||||
|
||||
func TestGroup_Services(t *testing.T) {
|
||||
group := SelectGroup(1)
|
||||
assert.NotEmpty(t, group.Services())
|
||||
}
|
||||
|
||||
func TestSelectGroups(t *testing.T) {
|
||||
groups := SelectGroups(true, false)
|
||||
assert.Equal(t, int(3), len(groups))
|
||||
groups = SelectGroups(true, true)
|
||||
assert.Equal(t, int(5), len(groups))
|
||||
}
|
||||
|
||||
func TestService_TotalFailures(t *testing.T) {
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailures()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestService_TotalFailures24(t *testing.T) {
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailures24()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestService_TotalFailuresOnDate(t *testing.T) {
|
||||
t.SkipNow()
|
||||
ago := utils.Now().UTC()
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailuresOnDate(ago)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestCountFailures(t *testing.T) {
|
||||
failures := CountFailures()
|
||||
assert.NotEqual(t, uint64(0), failures)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SparklineDayFailures returns a string array of daily service failures
|
||||
func (s *Service) SparklineDayFailures(days int) string {
|
||||
var arr []string
|
||||
ago := time.Now().UTC().Add((time.Duration(days) * -24) * time.Hour)
|
||||
for day := 1; day <= days; day++ {
|
||||
ago = ago.Add(24 * time.Hour)
|
||||
failures, _ := s.TotalFailuresOnDate(ago)
|
||||
arr = append(arr, utils.ToString(failures))
|
||||
}
|
||||
return "[" + strings.Join(arr, ",") + "]"
|
||||
}
|
||||
|
||||
// SparklineHourResponse returns a string array for the average response or ping time for a service
|
||||
func (s *Service) SparklineHourResponse(hours int, method string) string {
|
||||
var arr []string
|
||||
end := time.Now().UTC()
|
||||
start := end.Add(time.Duration(-hours) * time.Hour)
|
||||
obj := GraphDataRaw(s, start, end, "hour", method)
|
||||
for _, v := range obj.Array {
|
||||
arr = append(arr, utils.ToString(v.Value))
|
||||
}
|
||||
return "[" + strings.Join(arr, ",") + "]"
|
||||
}
|
115
core/users.go
115
core/users.go
|
@ -1,115 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
*types.User
|
||||
}
|
||||
|
||||
// ReturnUser returns *core.User based off a *types.User
|
||||
func ReturnUser(u *types.User) *User {
|
||||
return &User{u}
|
||||
}
|
||||
|
||||
// CountUsers returns the amount of users
|
||||
func CountUsers() int64 {
|
||||
var amount int64
|
||||
usersDB().Count(&amount)
|
||||
return amount
|
||||
}
|
||||
|
||||
// SelectUser returns the User based on the User's ID.
|
||||
func SelectUser(id int64) (*User, error) {
|
||||
var user User
|
||||
err := usersDB().Where("id = ?", id).First(&user)
|
||||
return &user, err.Error
|
||||
}
|
||||
|
||||
// SelectUsername returns the User based on the User's username
|
||||
func SelectUsername(username string) (*User, error) {
|
||||
var user User
|
||||
res := usersDB().Where("username = ?", username)
|
||||
err := res.First(&user)
|
||||
return &user, err.Error
|
||||
}
|
||||
|
||||
// Delete will remove the User record from the database
|
||||
func (u *User) Delete() error {
|
||||
return usersDB().Delete(u).Error
|
||||
}
|
||||
|
||||
// Update will update the User's record in database
|
||||
func (u *User) Update() error {
|
||||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
return usersDB().Update(u).Error
|
||||
}
|
||||
|
||||
// Create will insert a new User into the database
|
||||
func (u *User) Create() (int64, error) {
|
||||
u.CreatedAt = time.Now().UTC()
|
||||
u.Password = utils.HashPassword(u.Password)
|
||||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
db := usersDB().Create(u)
|
||||
if db.Error != nil {
|
||||
return 0, db.Error
|
||||
}
|
||||
if u.Id == 0 {
|
||||
log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
|
||||
return 0, db.Error
|
||||
}
|
||||
return u.Id, db.Error
|
||||
}
|
||||
|
||||
// SelectAllUsers returns all users
|
||||
func SelectAllUsers() ([]*User, error) {
|
||||
var users []*User
|
||||
db := usersDB().Find(&users)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error))
|
||||
return nil, db.Error
|
||||
}
|
||||
return users, db.Error
|
||||
}
|
||||
|
||||
// AuthUser will return the User and a boolean if authentication was correct.
|
||||
// AuthUser accepts username, and password as a string
|
||||
func AuthUser(username, password string) (*User, bool) {
|
||||
user, err := SelectUsername(username)
|
||||
if err != nil {
|
||||
log.Warnln(fmt.Errorf("user %v not found", username))
|
||||
return nil, false
|
||||
}
|
||||
if CheckHash(password, user.Password) {
|
||||
return user, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// CheckHash returns true if the password matches with a hashed bcrypt password
|
||||
func CheckHash(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
user := ReturnUser(&types.User{
|
||||
Username: "hunter",
|
||||
Password: "password123",
|
||||
Email: "test@email.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
userId, err := user.Create()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, userId)
|
||||
}
|
||||
|
||||
func TestSelectAllUsers(t *testing.T) {
|
||||
users, err := SelectAllUsers()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(users))
|
||||
}
|
||||
|
||||
func TestSelectUser(t *testing.T) {
|
||||
user, err := SelectUser(1)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "info@betatude.com", user.Email)
|
||||
assert.True(t, user.Admin.Bool)
|
||||
}
|
||||
|
||||
func TestSelectUsername(t *testing.T) {
|
||||
user, err := SelectUsername("hunter")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "test@email.com", user.Email)
|
||||
assert.Equal(t, int64(3), user.Id)
|
||||
assert.True(t, user.Admin.Bool)
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
user, err := SelectUser(1)
|
||||
assert.Nil(t, err)
|
||||
user.Username = "updated"
|
||||
err = user.Update()
|
||||
assert.Nil(t, err)
|
||||
updatedUser, err := SelectUser(1)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "updated", updatedUser.Username)
|
||||
}
|
||||
|
||||
func TestCreateUser2(t *testing.T) {
|
||||
user := ReturnUser(&types.User{
|
||||
Username: "hunterlong",
|
||||
Password: "password123",
|
||||
Email: "User@email.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
userId, err := user.Create()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, userId)
|
||||
}
|
||||
|
||||
func TestSelectAllUsersAgain(t *testing.T) {
|
||||
users, err := SelectAllUsers()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, len(users))
|
||||
}
|
||||
|
||||
func TestAuthUser(t *testing.T) {
|
||||
user, auth := AuthUser("hunterlong", "password123")
|
||||
assert.True(t, auth)
|
||||
assert.NotNil(t, user)
|
||||
assert.Equal(t, "User@email.com", user.Email)
|
||||
assert.Equal(t, int64(4), user.Id)
|
||||
assert.True(t, user.Admin.Bool)
|
||||
}
|
||||
|
||||
func TestFailedAuthUser(t *testing.T) {
|
||||
user, auth := AuthUser("hunterlong", "wrongpassword")
|
||||
assert.False(t, auth)
|
||||
assert.Nil(t, user)
|
||||
}
|
||||
|
||||
func TestCheckPassword(t *testing.T) {
|
||||
user, err := SelectUser(2)
|
||||
assert.Nil(t, err)
|
||||
pass := CheckHash("password123", user.Password)
|
||||
assert.True(t, pass)
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
user, err := SelectUser(2)
|
||||
assert.Nil(t, err)
|
||||
err = user.Delete()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestDbConfig_Close(t *testing.T) {
|
||||
err := DbSession.Close()
|
||||
assert.Nil(t, err)
|
||||
}
|
|
@ -0,0 +1,503 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/statping/statping/utils"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
)
|
||||
|
||||
const (
|
||||
TIME_NANO = "2006-01-02T15:04:05Z"
|
||||
TIME = "2006-01-02 15:04:05"
|
||||
CHART_TIME = "2006-01-02T15:04:05.999999-07:00"
|
||||
TIME_DAY = "2006-01-02"
|
||||
)
|
||||
|
||||
var database Database
|
||||
|
||||
// Database is an interface which DB implements
|
||||
type Database interface {
|
||||
Close() error
|
||||
DB() *sql.DB
|
||||
New() Database
|
||||
NewScope(value interface{}) *gorm.Scope
|
||||
CommonDB() gorm.SQLCommon
|
||||
Callback() *gorm.Callback
|
||||
SetLogger(l gorm.Logger)
|
||||
LogMode(enable bool) Database
|
||||
SingularTable(enable bool)
|
||||
Where(query interface{}, args ...interface{}) Database
|
||||
Or(query interface{}, args ...interface{}) Database
|
||||
Not(query interface{}, args ...interface{}) Database
|
||||
Limit(value int) Database
|
||||
Offset(value int) Database
|
||||
Order(value string, reorder ...bool) Database
|
||||
Select(query interface{}, args ...interface{}) Database
|
||||
Omit(columns ...string) Database
|
||||
Group(query string) Database
|
||||
Having(query string, values ...interface{}) Database
|
||||
Joins(query string, args ...interface{}) Database
|
||||
Scopes(funcs ...func(*gorm.DB) *gorm.DB) Database
|
||||
Unscoped() Database
|
||||
Attrs(attrs ...interface{}) Database
|
||||
Assign(attrs ...interface{}) Database
|
||||
First(out interface{}, where ...interface{}) Database
|
||||
Last(out interface{}, where ...interface{}) Database
|
||||
Find(out interface{}, where ...interface{}) Database
|
||||
Scan(dest interface{}) Database
|
||||
Row() *sql.Row
|
||||
Rows() (*sql.Rows, error)
|
||||
ScanRows(rows *sql.Rows, result interface{}) error
|
||||
Pluck(column string, value interface{}) Database
|
||||
Count(value interface{}) Database
|
||||
Related(value interface{}, foreignKeys ...string) Database
|
||||
FirstOrInit(out interface{}, where ...interface{}) Database
|
||||
FirstOrCreate(out interface{}, where ...interface{}) Database
|
||||
Update(attrs ...interface{}) Database
|
||||
Updates(values interface{}, ignoreProtectedAttrs ...bool) Database
|
||||
UpdateColumn(attrs ...interface{}) Database
|
||||
UpdateColumns(values interface{}) Database
|
||||
Save(value interface{}) Database
|
||||
Create(value interface{}) Database
|
||||
Delete(value interface{}, where ...interface{}) Database
|
||||
Raw(sql string, values ...interface{}) Database
|
||||
Exec(sql string, values ...interface{}) Database
|
||||
Model(value interface{}) Database
|
||||
Table(name string) Database
|
||||
Debug() Database
|
||||
Begin() Database
|
||||
Commit() Database
|
||||
Rollback() Database
|
||||
NewRecord(value interface{}) bool
|
||||
RecordNotFound() bool
|
||||
CreateTable(values ...interface{}) Database
|
||||
DropTable(values ...interface{}) Database
|
||||
DropTableIfExists(values ...interface{}) Database
|
||||
HasTable(value interface{}) bool
|
||||
AutoMigrate(values ...interface{}) Database
|
||||
ModifyColumn(column string, typ string) Database
|
||||
DropColumn(column string) Database
|
||||
AddIndex(indexName string, column ...string) Database
|
||||
AddUniqueIndex(indexName string, column ...string) Database
|
||||
RemoveIndex(indexName string) Database
|
||||
AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database
|
||||
Association(column string) *gorm.Association
|
||||
Preload(column string, conditions ...interface{}) Database
|
||||
Set(name string, value interface{}) Database
|
||||
InstantSet(name string, value interface{}) Database
|
||||
Get(name string) (value interface{}, ok bool)
|
||||
SetJoinTableHandler(source interface{}, column string, handler gorm.JoinTableHandlerInterface)
|
||||
AddError(err error) error
|
||||
GetErrors() (errors []error)
|
||||
|
||||
// extra
|
||||
Error() error
|
||||
RowsAffected() int64
|
||||
|
||||
Since(time.Time) Database
|
||||
Between(time.Time, time.Time) Database
|
||||
|
||||
SelectByTime(time.Duration) string
|
||||
MultipleSelects(args ...string) Database
|
||||
|
||||
FormatTime(t time.Time) string
|
||||
ParseTime(t string) (time.Time, error)
|
||||
DbType() string
|
||||
}
|
||||
|
||||
func (it *Db) DbType() string {
|
||||
return it.Database.Dialect().GetName()
|
||||
}
|
||||
|
||||
func Close(db Database) error {
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
func LogMode(db Database, b bool) Database {
|
||||
return db.LogMode(b)
|
||||
}
|
||||
|
||||
func Begin(db Database, model interface{}) Database {
|
||||
if all, ok := model.(string); ok {
|
||||
if all == "migration" {
|
||||
return db.Begin()
|
||||
}
|
||||
}
|
||||
return db.Model(model).Begin()
|
||||
}
|
||||
|
||||
func Available(db Database) bool {
|
||||
if db == nil {
|
||||
return false
|
||||
}
|
||||
if err := db.DB().Ping(); err != nil {
|
||||
return false
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
|
||||
}
|
||||
}
|
||||
|
||||
type Db struct {
|
||||
Database *gorm.DB
|
||||
Type string
|
||||
}
|
||||
|
||||
// Openw is a drop-in replacement for Open()
|
||||
func Openw(dialect string, args ...interface{}) (db Database, err error) {
|
||||
gorm.NowFunc = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
gormdb, err := gorm.Open(dialect, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
database = Wrap(gormdb)
|
||||
return database, err
|
||||
}
|
||||
|
||||
func OpenTester() (Database, error) {
|
||||
newDb, err := Openw("sqlite3", fmt.Sprintf("file:%s?mode=memory&cache=shared", utils.RandomString(12)))
|
||||
newDb.DB().SetMaxOpenConns(1)
|
||||
return newDb, err
|
||||
}
|
||||
|
||||
// Wrap wraps gorm.DB in an interface
|
||||
func Wrap(db *gorm.DB) Database {
|
||||
return &Db{
|
||||
Database: db,
|
||||
Type: db.Dialect().GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
func (it *Db) Close() error {
|
||||
return it.Database.Close()
|
||||
}
|
||||
|
||||
func (it *Db) DB() *sql.DB {
|
||||
return it.Database.DB()
|
||||
}
|
||||
|
||||
func (it *Db) New() Database {
|
||||
return Wrap(it.Database.New())
|
||||
}
|
||||
|
||||
func (it *Db) NewScope(value interface{}) *gorm.Scope {
|
||||
return it.Database.NewScope(value)
|
||||
}
|
||||
|
||||
func (it *Db) CommonDB() gorm.SQLCommon {
|
||||
return it.Database.CommonDB()
|
||||
}
|
||||
|
||||
func (it *Db) Callback() *gorm.Callback {
|
||||
return it.Database.Callback()
|
||||
}
|
||||
|
||||
func (it *Db) SetLogger(log gorm.Logger) {
|
||||
it.Database.SetLogger(log)
|
||||
}
|
||||
|
||||
func (it *Db) LogMode(enable bool) Database {
|
||||
return Wrap(it.Database.LogMode(enable))
|
||||
}
|
||||
|
||||
func (it *Db) SingularTable(enable bool) {
|
||||
it.Database.SingularTable(enable)
|
||||
}
|
||||
|
||||
func (it *Db) Where(query interface{}, args ...interface{}) Database {
|
||||
return Wrap(it.Database.Where(query, args...))
|
||||
}
|
||||
|
||||
func (it *Db) Or(query interface{}, args ...interface{}) Database {
|
||||
return Wrap(it.Database.Or(query, args...))
|
||||
}
|
||||
|
||||
func (it *Db) Not(query interface{}, args ...interface{}) Database {
|
||||
return Wrap(it.Database.Not(query, args...))
|
||||
}
|
||||
|
||||
func (it *Db) Limit(value int) Database {
|
||||
return Wrap(it.Database.Limit(value))
|
||||
}
|
||||
|
||||
func (it *Db) Offset(value int) Database {
|
||||
return Wrap(it.Database.Offset(value))
|
||||
}
|
||||
|
||||
func (it *Db) Order(value string, reorder ...bool) Database {
|
||||
return Wrap(it.Database.Order(value, reorder...))
|
||||
}
|
||||
|
||||
func (it *Db) Select(query interface{}, args ...interface{}) Database {
|
||||
return Wrap(it.Database.Select(query, args...))
|
||||
}
|
||||
|
||||
func (it *Db) Omit(columns ...string) Database {
|
||||
return Wrap(it.Database.Omit(columns...))
|
||||
}
|
||||
|
||||
func (it *Db) Group(query string) Database {
|
||||
return Wrap(it.Database.Group(query))
|
||||
}
|
||||
|
||||
func (it *Db) Having(query string, values ...interface{}) Database {
|
||||
return Wrap(it.Database.Having(query, values...))
|
||||
}
|
||||
|
||||
func (it *Db) Joins(query string, args ...interface{}) Database {
|
||||
return Wrap(it.Database.Joins(query, args...))
|
||||
}
|
||||
|
||||
func (it *Db) Scopes(funcs ...func(*gorm.DB) *gorm.DB) Database {
|
||||
return Wrap(it.Database.Scopes(funcs...))
|
||||
}
|
||||
|
||||
func (it *Db) Unscoped() Database {
|
||||
return Wrap(it.Database.Unscoped())
|
||||
}
|
||||
|
||||
func (it *Db) Attrs(attrs ...interface{}) Database {
|
||||
return Wrap(it.Database.Attrs(attrs...))
|
||||
}
|
||||
|
||||
func (it *Db) Assign(attrs ...interface{}) Database {
|
||||
return Wrap(it.Database.Assign(attrs...))
|
||||
}
|
||||
|
||||
func (it *Db) First(out interface{}, where ...interface{}) Database {
|
||||
return Wrap(it.Database.First(out, where...))
|
||||
}
|
||||
|
||||
func (it *Db) Last(out interface{}, where ...interface{}) Database {
|
||||
return Wrap(it.Database.Last(out, where...))
|
||||
}
|
||||
|
||||
func (it *Db) Find(out interface{}, where ...interface{}) Database {
|
||||
return Wrap(it.Database.Find(out, where...))
|
||||
}
|
||||
|
||||
func (it *Db) Scan(dest interface{}) Database {
|
||||
return Wrap(it.Database.Scan(dest))
|
||||
}
|
||||
|
||||
func (it *Db) Row() *sql.Row {
|
||||
return it.Database.Row()
|
||||
}
|
||||
|
||||
func (it *Db) Rows() (*sql.Rows, error) {
|
||||
return it.Database.Rows()
|
||||
}
|
||||
|
||||
func (it *Db) ScanRows(rows *sql.Rows, result interface{}) error {
|
||||
return it.Database.ScanRows(rows, result)
|
||||
}
|
||||
|
||||
func (it *Db) Pluck(column string, value interface{}) Database {
|
||||
return Wrap(it.Database.Pluck(column, value))
|
||||
}
|
||||
|
||||
func (it *Db) Count(value interface{}) Database {
|
||||
return Wrap(it.Database.Count(value))
|
||||
}
|
||||
|
||||
func (it *Db) Related(value interface{}, foreignKeys ...string) Database {
|
||||
return Wrap(it.Database.Related(value, foreignKeys...))
|
||||
}
|
||||
|
||||
func (it *Db) FirstOrInit(out interface{}, where ...interface{}) Database {
|
||||
return Wrap(it.Database.FirstOrInit(out, where...))
|
||||
}
|
||||
|
||||
func (it *Db) FirstOrCreate(out interface{}, where ...interface{}) Database {
|
||||
return Wrap(it.Database.FirstOrCreate(out, where...))
|
||||
}
|
||||
|
||||
func (it *Db) Update(attrs ...interface{}) Database {
|
||||
return Wrap(it.Database.Update(attrs...))
|
||||
}
|
||||
|
||||
func (it *Db) Updates(values interface{}, ignoreProtectedAttrs ...bool) Database {
|
||||
return Wrap(it.Database.Updates(values, ignoreProtectedAttrs...))
|
||||
}
|
||||
|
||||
func (it *Db) UpdateColumn(attrs ...interface{}) Database {
|
||||
return Wrap(it.Database.UpdateColumn(attrs...))
|
||||
}
|
||||
|
||||
func (it *Db) UpdateColumns(values interface{}) Database {
|
||||
return Wrap(it.Database.UpdateColumns(values))
|
||||
}
|
||||
|
||||
func (it *Db) Save(value interface{}) Database {
|
||||
return Wrap(it.Database.Save(value))
|
||||
}
|
||||
|
||||
func (it *Db) Create(value interface{}) Database {
|
||||
return Wrap(it.Database.Create(value))
|
||||
}
|
||||
|
||||
func (it *Db) Delete(value interface{}, where ...interface{}) Database {
|
||||
return Wrap(it.Database.Delete(value, where...))
|
||||
}
|
||||
|
||||
func (it *Db) Raw(sql string, values ...interface{}) Database {
|
||||
return Wrap(it.Database.Raw(sql, values...))
|
||||
}
|
||||
|
||||
func (it *Db) Exec(sql string, values ...interface{}) Database {
|
||||
return Wrap(it.Database.Exec(sql, values...))
|
||||
}
|
||||
|
||||
func (it *Db) Model(value interface{}) Database {
|
||||
return Wrap(it.Database.Model(value))
|
||||
}
|
||||
|
||||
func (it *Db) Table(name string) Database {
|
||||
return Wrap(it.Database.Table(name))
|
||||
}
|
||||
|
||||
func (it *Db) Debug() Database {
|
||||
return Wrap(it.Database.Debug())
|
||||
}
|
||||
|
||||
func (it *Db) Begin() Database {
|
||||
return Wrap(it.Database.Begin())
|
||||
}
|
||||
|
||||
func (it *Db) Commit() Database {
|
||||
return Wrap(it.Database.Commit())
|
||||
}
|
||||
|
||||
func (it *Db) Rollback() Database {
|
||||
return Wrap(it.Database.Rollback())
|
||||
}
|
||||
|
||||
func (it *Db) NewRecord(value interface{}) bool {
|
||||
return it.Database.NewRecord(value)
|
||||
}
|
||||
|
||||
func (it *Db) RecordNotFound() bool {
|
||||
return it.Database.RecordNotFound()
|
||||
}
|
||||
|
||||
func (it *Db) CreateTable(values ...interface{}) Database {
|
||||
return Wrap(it.Database.CreateTable(values...))
|
||||
}
|
||||
|
||||
func (it *Db) DropTable(values ...interface{}) Database {
|
||||
return Wrap(it.Database.DropTable(values...))
|
||||
}
|
||||
|
||||
func (it *Db) DropTableIfExists(values ...interface{}) Database {
|
||||
return Wrap(it.Database.DropTableIfExists(values...))
|
||||
}
|
||||
|
||||
func (it *Db) HasTable(value interface{}) bool {
|
||||
return it.Database.HasTable(value)
|
||||
}
|
||||
|
||||
func (it *Db) AutoMigrate(values ...interface{}) Database {
|
||||
return Wrap(it.Database.AutoMigrate(values...))
|
||||
}
|
||||
|
||||
func (it *Db) ModifyColumn(column string, typ string) Database {
|
||||
return Wrap(it.Database.ModifyColumn(column, typ))
|
||||
}
|
||||
|
||||
func (it *Db) DropColumn(column string) Database {
|
||||
return Wrap(it.Database.DropColumn(column))
|
||||
}
|
||||
|
||||
func (it *Db) AddIndex(indexName string, columns ...string) Database {
|
||||
return Wrap(it.Database.AddIndex(indexName, columns...))
|
||||
}
|
||||
|
||||
func (it *Db) AddUniqueIndex(indexName string, columns ...string) Database {
|
||||
return Wrap(it.Database.AddUniqueIndex(indexName, columns...))
|
||||
}
|
||||
|
||||
func (it *Db) RemoveIndex(indexName string) Database {
|
||||
return Wrap(it.Database.RemoveIndex(indexName))
|
||||
}
|
||||
|
||||
func (it *Db) Association(column string) *gorm.Association {
|
||||
return it.Database.Association(column)
|
||||
}
|
||||
|
||||
func (it *Db) Preload(column string, conditions ...interface{}) Database {
|
||||
return Wrap(it.Database.Preload(column, conditions...))
|
||||
}
|
||||
|
||||
func (it *Db) Set(name string, value interface{}) Database {
|
||||
return Wrap(it.Database.Set(name, value))
|
||||
}
|
||||
|
||||
func (it *Db) InstantSet(name string, value interface{}) Database {
|
||||
return Wrap(it.Database.InstantSet(name, value))
|
||||
}
|
||||
|
||||
func (it *Db) Get(name string) (interface{}, bool) {
|
||||
return it.Database.Get(name)
|
||||
}
|
||||
|
||||
func (it *Db) SetJoinTableHandler(source interface{}, column string, handler gorm.JoinTableHandlerInterface) {
|
||||
it.Database.SetJoinTableHandler(source, column, handler)
|
||||
}
|
||||
|
||||
func (it *Db) AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database {
|
||||
return Wrap(it.Database.AddForeignKey(field, dest, onDelete, onUpdate))
|
||||
}
|
||||
|
||||
func (it *Db) AddError(err error) error {
|
||||
return it.Database.AddError(err)
|
||||
}
|
||||
|
||||
func (it *Db) GetErrors() (errors []error) {
|
||||
return it.Database.GetErrors()
|
||||
}
|
||||
|
||||
func (it *Db) RowsAffected() int64 {
|
||||
return it.Database.RowsAffected
|
||||
}
|
||||
|
||||
func (it *Db) Error() error {
|
||||
return it.Database.Error
|
||||
}
|
||||
|
||||
func (it *Db) Since(ago time.Time) Database {
|
||||
return it.Where("created_at > ?", it.FormatTime(ago))
|
||||
}
|
||||
|
||||
func (it *Db) Between(t1 time.Time, t2 time.Time) Database {
|
||||
return it.Where("created_at BETWEEN ? AND ?", it.FormatTime(t1), it.FormatTime(t2))
|
||||
}
|
||||
|
||||
type TimeValue struct {
|
||||
Timeframe string `json:"timeframe"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/statping/statping/types"
|
||||
"github.com/statping/statping/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GroupBy struct {
|
||||
db Database
|
||||
query *GroupQuery
|
||||
}
|
||||
|
||||
type GroupByer interface {
|
||||
ToTimeValue() (*TimeVar, error)
|
||||
}
|
||||
|
||||
type By string
|
||||
|
||||
func (b By) String() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
type GroupQuery struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Group time.Duration
|
||||
Order string
|
||||
Limit int
|
||||
Offset int
|
||||
FillEmpty bool
|
||||
|
||||
db Database
|
||||
}
|
||||
|
||||
func (b GroupQuery) Find(data interface{}) error {
|
||||
return b.db.Find(data).Error()
|
||||
}
|
||||
|
||||
func (b GroupQuery) Database() Database {
|
||||
return b.db
|
||||
}
|
||||
|
||||
var (
|
||||
ByCount = By("COUNT(id) as amount")
|
||||
ByAverage = func(column string, multiplier int) By {
|
||||
switch database.DbType() {
|
||||
case "mysql":
|
||||
return By(fmt.Sprintf("CAST(AVG(%s) as UNSIGNED) as amount", column))
|
||||
case "postgres":
|
||||
return By(fmt.Sprintf("cast(AVG(%s) as int) as amount", column))
|
||||
default:
|
||||
return By(fmt.Sprintf("cast(AVG(%s) as int) as amount", column))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
type TimeVar struct {
|
||||
g *GroupQuery
|
||||
data []*TimeValue
|
||||
}
|
||||
|
||||
func (t *TimeVar) ToValues() ([]*TimeValue, error) {
|
||||
return t.data, nil
|
||||
}
|
||||
|
||||
// GraphData will return all hits or failures
|
||||
func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
||||
|
||||
dbQuery := g.db.MultipleSelects(
|
||||
g.db.SelectByTime(g.Group),
|
||||
by.String(),
|
||||
).Group("timeframe")
|
||||
|
||||
g.db = dbQuery
|
||||
|
||||
caller, err := g.ToTimeValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if g.FillEmpty {
|
||||
return caller.FillMissing(g.Start, g.End)
|
||||
}
|
||||
return caller.ToValues()
|
||||
}
|
||||
|
||||
func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
||||
rows, err := g.db.Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []*TimeValue
|
||||
for rows.Next() {
|
||||
var timeframe string
|
||||
amount := int64(0)
|
||||
if err := rows.Scan(&timeframe, &amount); err != nil {
|
||||
log.Error(err, timeframe)
|
||||
}
|
||||
trueTime, _ := g.db.ParseTime(timeframe)
|
||||
newTs := types.FixedTime(trueTime, g.Group)
|
||||
data = append(data, &TimeValue{
|
||||
Timeframe: newTs,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
return &TimeVar{g, 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 {
|
||||
var amount int64
|
||||
if timeMap[currentStr] != 0 {
|
||||
amount = timeMap[currentStr]
|
||||
}
|
||||
validSet = append(validSet, &TimeValue{
|
||||
Timeframe: currentStr,
|
||||
Amount: amount,
|
||||
})
|
||||
if current.After(end) {
|
||||
break
|
||||
}
|
||||
current = current.Add(dur)
|
||||
currentStr = types.FixedTime(current, t.g.Group)
|
||||
}
|
||||
|
||||
return validSet, nil
|
||||
}
|
||||
|
||||
type isObject interface {
|
||||
Db() Database
|
||||
}
|
||||
|
||||
func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
limit := utils.ToInt(fields.Get("limit"))
|
||||
offset := utils.ToInt(fields.Get("offset"))
|
||||
fill, _ := strconv.ParseBool(fields.Get("fill"))
|
||||
orderBy := fields.Get("order")
|
||||
if limit == 0 {
|
||||
limit = 10000
|
||||
}
|
||||
|
||||
q := o.Db()
|
||||
|
||||
if grouping == "" {
|
||||
grouping = "1h"
|
||||
}
|
||||
groupDur, err := time.ParseDuration(grouping)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
groupDur = 1 * time.Hour
|
||||
}
|
||||
|
||||
query := &GroupQuery{
|
||||
Start: time.Unix(startField, 0).UTC(),
|
||||
End: time.Unix(endField, 0).UTC(),
|
||||
Group: groupDur,
|
||||
Order: orderBy,
|
||||
Limit: int(limit),
|
||||
Offset: int(offset),
|
||||
FillEmpty: fill,
|
||||
db: q,
|
||||
}
|
||||
|
||||
if query.Start.After(query.End) {
|
||||
return nil, errors.New("start time is after ending time")
|
||||
}
|
||||
|
||||
if startField == 0 {
|
||||
query.Start = utils.Now().Add(-7 * types.Day)
|
||||
}
|
||||
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)
|
||||
}
|
||||
if query.Offset > 0 {
|
||||
q = q.Offset(query.Offset)
|
||||
}
|
||||
|
||||
q = q.Where("created_at BETWEEN ? AND ?", q.FormatTime(query.Start), q.FormatTime(query.End))
|
||||
|
||||
if query.Order != "" {
|
||||
q = q.Order(query.Order)
|
||||
}
|
||||
query.db = q
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func parseForm(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.PostForm
|
||||
}
|
||||
|
||||
func parseGet(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.Form
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package database
|
||||
|
||||
type DbObject interface {
|
||||
Create() error
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
|
||||
type Sampler interface {
|
||||
Sample() DbObject
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/statping/statping/types"
|
||||
"github.com/statping/statping/utils"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
)
|
||||
|
||||
var (
|
||||
log = utils.Log
|
||||
removeRowsAfter = types.Month * 6
|
||||
maintenceDuration = types.Hour
|
||||
)
|
||||
|
||||
func StartMaintenceRoutine() {
|
||||
dur := os.Getenv("REMOVE_AFTER")
|
||||
var removeDur time.Duration
|
||||
|
||||
if dur != "" {
|
||||
parsedDur, err := time.ParseDuration(dur)
|
||||
if err != nil {
|
||||
log.Errorf("could not parse duration: %s, using default: %s", dur, removeRowsAfter.String())
|
||||
removeDur = removeRowsAfter
|
||||
} else {
|
||||
removeDur = parsedDur
|
||||
}
|
||||
} else {
|
||||
removeDur = removeRowsAfter
|
||||
}
|
||||
|
||||
log.Infof("Service Failure and Hit records will be automatically removed after %s", removeDur.String())
|
||||
go databaseMaintence(removeDur)
|
||||
}
|
||||
|
||||
// databaseMaintence will automatically delete old records from 'failures' and 'hits'
|
||||
// this function is currently set to delete records 7+ days old every 60 minutes
|
||||
func databaseMaintence(dur time.Duration) {
|
||||
//deleteAfter := time.Now().UTC().Add(dur)
|
||||
|
||||
time.Sleep(20 * types.Second)
|
||||
|
||||
for range time.Tick(maintenceDuration) {
|
||||
log.Infof("Deleting failures older than %s", dur.String())
|
||||
//DeleteAllSince("failures", deleteAfter)
|
||||
|
||||
log.Infof("Deleting hits older than %s", dur.String())
|
||||
//DeleteAllSince("hits", deleteAfter)
|
||||
|
||||
maintenceDuration = types.Hour
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteAllSince will delete a specific table's records based on a time.
|
||||
func DeleteAllSince(table string, date time.Time) {
|
||||
sql := fmt.Sprintf("DELETE FROM %s WHERE created_at < '%s';", table, database.FormatTime(date))
|
||||
q := database.Exec(sql).Debug()
|
||||
if q.Error() != nil {
|
||||
log.Warnln(q.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimeGroup interface {
|
||||
}
|
||||
|
||||
func (it *Db) ParseTime(t string) (time.Time, error) {
|
||||
switch it.Type {
|
||||
case "mysql":
|
||||
return time.Parse("2006-01-02 15:04:05", t)
|
||||
case "postgres":
|
||||
return time.Parse("2006-01-02T15:04:05Z", t)
|
||||
default:
|
||||
return time.Parse("2006-01-02 15:04:05", t)
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
}
|
||||
|
||||
func (it *Db) SelectByTime(increment time.Duration) string {
|
||||
seconds := int(increment.Seconds())
|
||||
switch it.Type {
|
||||
case "mysql":
|
||||
return fmt.Sprintf("FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(created_at) / %d) * %d) AS timeframe", seconds, seconds)
|
||||
case "postgres":
|
||||
return fmt.Sprintf("date_trunc('%s', created_at) AS timeframe", increment)
|
||||
default:
|
||||
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
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,17 +0,0 @@
|
|||
FROM golang:1.13.5-alpine as base
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
ARG VERSION
|
||||
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
WORKDIR /go/src/github.com/hunterlong/statping
|
||||
ADD . /go/src/github.com/hunterlong/statping
|
||||
RUN go mod vendor
|
||||
RUN make dev-deps
|
||||
RUN make install
|
||||
|
||||
ENV IS_DOCKER=true
|
||||
ENV STATPING_DIR=/app
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["statping"]
|
|
@ -1,15 +0,0 @@
|
|||
FROM cypress/browsers:chrome67
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
# Statping 'test' image for running a full test using the production environment
|
||||
|
||||
WORKDIR $HOME/statping
|
||||
ADD dev/test .
|
||||
|
||||
RUN npm install node-sass
|
||||
ENV SASS=node-sass
|
||||
RUN npm install
|
||||
|
||||
ADD ./statping-linux-amd64 /usr/local/bin/statping
|
||||
RUN statping version
|
||||
|
||||
RUN npm run test-docker
|
|
@ -1,26 +0,0 @@
|
|||
FROM alpine
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
|
||||
ENV STATPING_VERSION=0.80.35
|
||||
|
||||
RUN apk add --no-cache ca-certificates linux-headers curl
|
||||
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
|
||||
RUN curl -L -s https://github.com/hunterlong/statping/releases/download/v$STATPING_VERSION/statping-linux-alpine.tar.gz | tar -xz && \
|
||||
chmod +x statping && mv statping /usr/local/bin/statping
|
||||
|
||||
ENV DB_CONN=sqlite
|
||||
ENV NAME="Statping Demo"
|
||||
ENV DESCRIPTION="An Awesome Demo of a Statping Server running on Docker"
|
||||
ENV DOMAIN=demo.statping.com
|
||||
ENV SASS=/usr/local/bin/sass
|
||||
|
||||
ENV IS_DOCKER=true
|
||||
ENV STATPING_DIR=/app
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./dev/demo-script.sh /app/
|
||||
|
||||
ENTRYPOINT ./demo-script.sh
|
|
@ -0,0 +1,36 @@
|
|||
FROM golang:1.13.5-alpine
|
||||
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 libsass nodejs nodejs-npm
|
||||
RUN npm install -g yarn
|
||||
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
|
||||
WORKDIR /go/src/github.com/hunterlong/statping
|
||||
|
||||
ADD go.mod go.sum version.txt ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
RUN go get github.com/stretchr/testify/... && \
|
||||
go get github.com/GeertJohan/go.rice/rice && \
|
||||
go get github.com/cortesi/modd/cmd/modd
|
||||
|
||||
ADD frontend/package.json frontend/yarn.lock ./frontend/
|
||||
|
||||
RUN cd frontend && yarn install --pure-lockfile --network-timeout 1000000 && yarn cache clean
|
||||
|
||||
ENV IS_DOCKER=true
|
||||
ENV STATPING_DIR=/go/src/github.com/hunterlong/statping
|
||||
|
||||
EXPOSE 8585
|
||||
EXPOSE 8888
|
||||
|
||||
RUN cd frontend && yarn build && cp -R dist ../source/
|
||||
|
||||
RUN pwd && ls
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD dev/dev-env.sh
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
0 problems (0 errors) (0 warnings)
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
COMMAND="rm -rf /app/statping.db && reboot"
|
||||
|
||||
echo "* * * * * echo $COMMAND >> /test_file 2>&1" > /etc/crontabs/root
|
||||
|
||||
statping
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
echo "Starting!"
|
||||
|
||||
echo "Serving Vue frontend first..."
|
||||
|
||||
cd frontend && yarn serve &
|
||||
|
||||
echo "Now serving Vue, lets build the golang backend now..."
|
||||
|
||||
modd -f dev/modd.conf
|
|
@ -1,86 +0,0 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
networks:
|
||||
- internet
|
||||
- database
|
||||
depends_on:
|
||||
- postgres
|
||||
volumes:
|
||||
- ./statping:/app
|
||||
environment:
|
||||
VIRTUAL_HOST: localhost
|
||||
VIRTUAL_PORT: 8080
|
||||
LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}
|
||||
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
|
||||
DB_CONN: postgres
|
||||
DB_HOST: postgres
|
||||
DB_USER: statping
|
||||
DB_PASS: password123
|
||||
DB_DATABASE: statping
|
||||
NAME: EC2 Example
|
||||
DESCRIPTION: This is a Statping Docker Compose instance
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: jwilder/nginx-proxy
|
||||
ports:
|
||||
- 0.0.0.0:80:80
|
||||
- 0.0.0.0:443:443
|
||||
networks:
|
||||
- internet
|
||||
restart: always
|
||||
labels:
|
||||
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./nginx/certs:/etc/nginx/certs:ro
|
||||
- ./nginx/vhost:/etc/nginx/vhost.d
|
||||
- ./nginx/html:/usr/share/nginx/html:ro
|
||||
- ./nginx/dhparam:/etc/nginx/dhparam
|
||||
environment:
|
||||
DEFAULT_HOST: localhost
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres
|
||||
restart: always
|
||||
networks:
|
||||
- database
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password123
|
||||
POSTGRES_USER: statping
|
||||
POSTGRES_DB: statping
|
||||
|
||||
letsencrypt:
|
||||
container_name: letsencrypt
|
||||
image: jrcs/letsencrypt-nginx-proxy-companion
|
||||
networks:
|
||||
- internet
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./nginx/certs:/etc/nginx/certs
|
||||
- ./nginx/vhost:/etc/nginx/vhost.d
|
||||
- ./nginx/html:/usr/share/nginx/html
|
||||
- ./nginx/dhparam:/etc/nginx/dhparam
|
||||
|
||||
watchtower:
|
||||
image: v2tec/watchtower
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: --interval 120 --cleanup
|
||||
|
||||
networks:
|
||||
internet:
|
||||
driver: bridge
|
||||
database:
|
||||
driver: bridge
|
|
@ -1,21 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: nginx:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
ports:
|
||||
- 80:80
|
||||
expose:
|
||||
- 80
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
expose:
|
||||
- 8080
|
|
@ -1,62 +0,0 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: jwilder/nginx-proxy
|
||||
ports:
|
||||
- 0.0.0.0:80:80
|
||||
- 0.0.0.0:443:443
|
||||
networks:
|
||||
- internet
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./statping/nginx/certs:/etc/nginx/certs:ro
|
||||
- ./statping/nginx/vhost:/etc/nginx/vhost.d
|
||||
- ./statping/nginx/html:/usr/share/nginx/html:ro
|
||||
- ./statping/nginx/dhparam:/etc/nginx/dhparam
|
||||
environment:
|
||||
DEFAULT_HOST: localhost
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
networks:
|
||||
- internet
|
||||
- database
|
||||
depends_on:
|
||||
- postgres_statping
|
||||
volumes:
|
||||
- ./statping/app:/app
|
||||
environment:
|
||||
VIRTUAL_HOST: localhost
|
||||
VIRTUAL_PORT: 8080
|
||||
DB_CONN: postgres
|
||||
DB_HOST: postgres_statping
|
||||
DB_USER: statping
|
||||
DB_PASS: password123
|
||||
DB_DATABASE: statping
|
||||
NAME: EC2 Example
|
||||
DESCRIPTION: This is a Statping Docker Compose instance
|
||||
|
||||
postgres_statping:
|
||||
container_name: postgres_statping
|
||||
image: postgres
|
||||
restart: always
|
||||
networks:
|
||||
- database
|
||||
volumes:
|
||||
- ./statping/postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password123
|
||||
POSTGRES_USER: statping
|
||||
POSTGRES_DB: statping
|
||||
|
||||
networks:
|
||||
internet:
|
||||
driver: bridge
|
||||
database:
|
||||
driver: bridge
|
|
@ -1,17 +0,0 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
ports:
|
||||
- 80:8080
|
||||
volumes:
|
||||
- ./statping:/app
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
NAME: Statping
|
||||
DESCRIPTION: You'll need to input your own Database information in Setup process
|
|
@ -1,86 +0,0 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: jwilder/nginx-proxy
|
||||
ports:
|
||||
- 0.0.0.0:80:80
|
||||
- 0.0.0.0:443:443
|
||||
labels:
|
||||
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
|
||||
networks:
|
||||
- internet
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./statping/nginx/certs:/etc/nginx/certs:ro
|
||||
- ./statping/nginx/vhost:/etc/nginx/vhost.d
|
||||
- ./statping/nginx/html:/usr/share/nginx/html:ro
|
||||
- ./statping/nginx/dhparam:/etc/nginx/dhparam
|
||||
environment:
|
||||
DEFAULT_HOST: ${LETSENCRYPT_HOST}
|
||||
|
||||
letsencrypt:
|
||||
container_name: letsencrypt
|
||||
image: jrcs/letsencrypt-nginx-proxy-companion
|
||||
networks:
|
||||
- internet
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./statping/nginx/certs:/etc/nginx/certs
|
||||
- ./statping/nginx/vhost:/etc/nginx/vhost.d
|
||||
- ./statping/nginx/html:/usr/share/nginx/html
|
||||
- ./statping/nginx/dhparam:/etc/nginx/dhparam
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
networks:
|
||||
- internet
|
||||
- database
|
||||
depends_on:
|
||||
- postgres
|
||||
volumes:
|
||||
- ./statping/app:/app
|
||||
environment:
|
||||
VIRTUAL_HOST: ${LETSENCRYPT_HOST}
|
||||
VIRTUAL_PORT: 8080
|
||||
LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}
|
||||
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
|
||||
DB_CONN: postgres
|
||||
DB_HOST: postgres
|
||||
DB_USER: statping
|
||||
DB_PASS: password123
|
||||
DB_DATABASE: statping
|
||||
NAME: Statping SSL Instance
|
||||
DESCRIPTION: This Statping Status Page should be running ${LETSENCRYPT_HOST} with SSL.
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres
|
||||
restart: always
|
||||
networks:
|
||||
- database
|
||||
volumes:
|
||||
- ./statping/postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password123
|
||||
POSTGRES_USER: statping
|
||||
POSTGRES_DB: statping
|
||||
|
||||
watchtower:
|
||||
image: v2tec/watchtower
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: --interval 120 --cleanup
|
||||
|
||||
networks:
|
||||
internet:
|
||||
driver: bridge
|
||||
database:
|
||||
driver: bridge
|
|
@ -0,0 +1,98 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres
|
||||
volumes:
|
||||
- ../docker/databases/postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password123
|
||||
POSTGRES_DB: statping
|
||||
POSTGRES_USER: root
|
||||
ports:
|
||||
- 5432:5432
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U root"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 20
|
||||
|
||||
mysql:
|
||||
container_name: mysql
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- ../docker/databases/mysql:/var/lib/mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password123
|
||||
MYSQL_DATABASE: statping
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: password123
|
||||
ports:
|
||||
- 3306:3306
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||
timeout: 20s
|
||||
interval: 15s
|
||||
retries: 30
|
||||
|
||||
phpmyadmin:
|
||||
container_name: phpmyadmin
|
||||
image: phpmyadmin/phpmyadmin
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 5050:80
|
||||
links:
|
||||
- mysql:db
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password123
|
||||
PMA_HOST: mysql
|
||||
PMA_USER: root
|
||||
PMA_PASSWORD: password123
|
||||
PMA_PORT: 3306
|
||||
|
||||
sqlite-web:
|
||||
container_name: sqlite-web
|
||||
image: coleifer/sqlite-web
|
||||
restart: on-failure
|
||||
command: sqlite_web -H 0.0.0.0 -r -x /data/statping.db
|
||||
ports:
|
||||
- 6050:8080
|
||||
volumes:
|
||||
- ../docker/statping/sqlite/statping.db:/data/statping.db:ro
|
||||
environment:
|
||||
SQLITE_DATABASE: /data/statping.db
|
||||
|
||||
pgadmin4:
|
||||
container_name: pgadmin4
|
||||
image: fenglc/pgadmin4
|
||||
restart: on-failure
|
||||
environment:
|
||||
DEFAULT_USER: admin@admin.com
|
||||
DEFAULT_PASSWORD: admin
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 7000:5050
|
||||
links:
|
||||
- postgres:postgres
|
||||
|
||||
prometheus:
|
||||
container_name: prometheus
|
||||
image: prom/prometheus:v2.0.0
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- ../docker/databases/prometheus:/prometheus
|
||||
ports:
|
||||
- 7050:9090
|
||||
healthcheck:
|
||||
test: "/bin/wget -q -Y off http://localhost:9090/status -O /dev/null > /dev/null 2>&1"
|
||||
interval: 10s
|
||||
timeout: 3s
|
|
@ -0,0 +1,303 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
nginx-proxy:
|
||||
image: jwilder/nginx-proxy
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
|
||||
# statping_dev:
|
||||
# container_name: statping_dev
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ./dev/Dockerfile.dev
|
||||
# args:
|
||||
# VERSION: DEV
|
||||
# COMMIT: DEV
|
||||
# restart: on-failure
|
||||
# volumes:
|
||||
# - ./:/go/src/github.com/hunterlong/statping
|
||||
# environment:
|
||||
# VIRTUAL_HOST: local.statping.com
|
||||
# VIRTUAL_PORT: 8888
|
||||
# GO_ENV: test
|
||||
# DB_CONN: sqlite
|
||||
# API_KEY: exampleapikey
|
||||
# API_SECRET: exampleapisecret
|
||||
# NAME: Statping on SQLite
|
||||
# DOMAIN: http://localhost:4000
|
||||
# DESCRIPTION: This is a dev environment on SQLite!
|
||||
# ADMIN_USER: admin
|
||||
# ADMIN_PASS: admin
|
||||
# PORT: 8585
|
||||
# SERVICES: '[{"name": "Local Statping", "type": "http", "domain": "http://localhost:8585", "interval": 30}]'
|
||||
# ports:
|
||||
# - 8888:8888
|
||||
# - 8585:8585
|
||||
# networks:
|
||||
# - statping
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "curl -f http://localhost:8585/health || false"]
|
||||
# timeout: 2s
|
||||
# interval: 20s
|
||||
# retries: 30
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
build:
|
||||
context: ./
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./docker/statping/sqlite:/app
|
||||
environment:
|
||||
SERVICES: '[{"name": "Local Statping", "type": "http", "domain": "http://localhost:8585", "interval": 30}]'
|
||||
VIRTUAL_HOST: sqlite.dev.statping.com
|
||||
VIRTUAL_PORT: 8080
|
||||
DB_CONN: sqlite
|
||||
API_KEY: exampleapikey
|
||||
API_SECRET: exampleapisecret
|
||||
NAME: Statping on SQLite
|
||||
DOMAIN: http://localhost:4000
|
||||
DESCRIPTION: This is a dev environment on SQLite!
|
||||
ADMIN_USER: admin
|
||||
ADMIN_PASS: admin
|
||||
ports:
|
||||
- 4000:8080
|
||||
networks:
|
||||
- statping
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
|
||||
timeout: 2s
|
||||
interval: 10s
|
||||
retries: 20
|
||||
|
||||
statping_mysql:
|
||||
container_name: statping_mysql
|
||||
build:
|
||||
context: ./
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 4005:8080
|
||||
volumes:
|
||||
- ./docker/statping/mysql:/app
|
||||
links:
|
||||
- mysql
|
||||
environment:
|
||||
VIRTUAL_HOST: mysql.dev.statping.com
|
||||
VIRTUAL_PORT: 8080
|
||||
DB_CONN: mysql
|
||||
DB_HOST: mysql
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: statping
|
||||
DB_USER: root
|
||||
DB_PASS: password123
|
||||
API_KEY: exampleapikey
|
||||
API_SECRET: exampleapisecret
|
||||
NAME: Statping on MySQL
|
||||
DOMAIN: http://localhost:4005
|
||||
DESCRIPTION: This is a dev environment on MySQL!
|
||||
ADMIN_USER: admin
|
||||
ADMIN_PASS: admin
|
||||
networks:
|
||||
- statping
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
|
||||
timeout: 2s
|
||||
interval: 10s
|
||||
retries: 20
|
||||
|
||||
statping_postgres:
|
||||
container_name: statping_postgres
|
||||
build:
|
||||
context: ./
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 4010:8080
|
||||
volumes:
|
||||
- ./docker/statping/postgres:/app
|
||||
links:
|
||||
- postgres
|
||||
environment:
|
||||
VIRTUAL_HOST: postgres.dev.statping.com
|
||||
VIRTUAL_PORT: 8080
|
||||
DB_CONN: postgres
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_DATABASE: statping
|
||||
DB_USER: root
|
||||
DB_PASS: password123
|
||||
API_KEY: exampleapikey
|
||||
API_SECRET: exampleapisecret
|
||||
NAME: Statping on Postgres
|
||||
DOMAIN: http://localhost:4010
|
||||
DESCRIPTION: This is a dev environment on Postgres!
|
||||
ADMIN_USER: admin
|
||||
ADMIN_PASS: admin
|
||||
networks:
|
||||
- statping
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 30
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres
|
||||
volumes:
|
||||
- ./docker/databases/postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password123
|
||||
POSTGRES_DB: statping
|
||||
POSTGRES_USER: root
|
||||
networks:
|
||||
- statping
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U root"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 20
|
||||
|
||||
mysql:
|
||||
container_name: mysql
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- ./docker/databases/mysql:/var/lib/mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password123
|
||||
MYSQL_DATABASE: statping
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: password
|
||||
networks:
|
||||
- statping
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||
timeout: 20s
|
||||
interval: 15s
|
||||
retries: 30
|
||||
|
||||
phpmyadmin:
|
||||
container_name: phpmyadmin
|
||||
image: phpmyadmin/phpmyadmin
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 5050:80
|
||||
links:
|
||||
- mysql:db
|
||||
environment:
|
||||
VIRTUAL_HOST: phpmyadmin.statping.com
|
||||
VIRTUAL_PORT: 80
|
||||
MYSQL_ROOT_PASSWORD: password123
|
||||
PMA_HOST: mysql
|
||||
PMA_USER: root
|
||||
PMA_PASSWORD: password123
|
||||
PMA_PORT: 3306
|
||||
networks:
|
||||
- statping
|
||||
|
||||
sqlite-web:
|
||||
container_name: sqlite-web
|
||||
image: coleifer/sqlite-web
|
||||
restart: on-failure
|
||||
command: sqlite_web -H 0.0.0.0 -r -x /data/statping.db
|
||||
depends_on:
|
||||
statping:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 6050:8080
|
||||
links:
|
||||
- statping
|
||||
volumes:
|
||||
- ./docker/statping/sqlite/statping.db:/data/statping.db:ro
|
||||
environment:
|
||||
VIRTUAL_HOST: sqladmin.statping.com
|
||||
VIRTUAL_PORT: 8080
|
||||
SQLITE_DATABASE: /data/statping.db
|
||||
networks:
|
||||
- statping
|
||||
|
||||
pgadmin4:
|
||||
container_name: pgadmin4
|
||||
image: fenglc/pgadmin4
|
||||
restart: on-failure
|
||||
environment:
|
||||
VIRTUAL_HOST: pgadmin.statping.com
|
||||
VIRTUAL_PORT: 5050
|
||||
DEFAULT_USER: admin@admin.com
|
||||
DEFAULT_PASSWORD: admin
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 7000:5050
|
||||
links:
|
||||
- postgres:postgres
|
||||
networks:
|
||||
- statping
|
||||
|
||||
prometheus:
|
||||
container_name: prometheus
|
||||
image: prom/prometheus:v2.0.0
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./dev/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- ./docker/databases/prometheus:/prometheus
|
||||
links:
|
||||
- statping
|
||||
- statping_mysql
|
||||
- statping_postgres
|
||||
ports:
|
||||
- 7050:9090
|
||||
networks:
|
||||
- statping
|
||||
environment:
|
||||
VIRTUAL_HOST: prometheus.statping.com
|
||||
VIRTUAL_PORT: 9090
|
||||
healthcheck:
|
||||
test: "/bin/wget -q -Y off http://localhost:9090/status -O /dev/null > /dev/null 2>&1"
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
|
||||
grafana:
|
||||
container_name: grafana
|
||||
image: grafana/grafana
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- ./docker/grafana:/var/lib/grafana
|
||||
- ./dev/grafana/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
|
||||
- ./dev/grafana/dashboard.yml:/etc/grafana/provisioning/dashboards/dashboard.yml
|
||||
- ./dev/grafana/statping_dashboard.json:/etc/grafana/provisioning/dashboards/statping_dashboard.json
|
||||
environment:
|
||||
- VIRTUAL_HOST=grafana.statping.com
|
||||
- VIRTUAL_PORT=3000
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
- GF_AUTH_ANONYMOUS_ENABLED=true
|
||||
depends_on:
|
||||
prometheus:
|
||||
condition: service_healthy
|
||||
links:
|
||||
- prometheus
|
||||
networks:
|
||||
- statping
|
||||
healthcheck:
|
||||
test: "/usr/bin/wget -q -Y off http://localhost:3000/api/health -O /dev/null > /dev/null 2>&1"
|
||||
interval: 10s
|
||||
retries: 20
|
||||
|
||||
networks:
|
||||
statping:
|
|
@ -0,0 +1,42 @@
|
|||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
statping_dev:
|
||||
container_name: statping_dev
|
||||
image: hunterlong/statping:dev
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./cmd:/go/src/github.com/hunterlong/statping/cmd/
|
||||
- ./core:/go/src/github.com/hunterlong/statping/core/
|
||||
- ./database:/go/src/github.com/hunterlong/statping/database/
|
||||
- ./dev:/go/src/github.com/hunterlong/statping/dev/
|
||||
- ./frontend:/go/src/github.com/hunterlong/statping/frontend/
|
||||
- ./handlers:/go/src/github.com/hunterlong/statping/handlers/
|
||||
- ./notifiers:/go/src/github.com/hunterlong/statping/notifiers/
|
||||
- ./source:/go/src/github.com/hunterlong/statping/source/
|
||||
- ./types:/go/src/github.com/hunterlong/statping/types/
|
||||
- ./utils:/go/src/github.com/hunterlong/statping/utils/
|
||||
environment:
|
||||
DB_CONN: sqlite
|
||||
API_KEY: exampleapikey
|
||||
API_SECRET: exampleapisecret
|
||||
NAME: Statping
|
||||
DOMAIN: http://localhost:8585
|
||||
DESCRIPTION: This is a dev environment with auto reloading!
|
||||
ADMIN_USER: admin
|
||||
ADMIN_PASS: admin
|
||||
PORT: 8585
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 8585:8585
|
||||
networks:
|
||||
- statping
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8585/health || false"]
|
||||
timeout: 2s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
statping:
|
|
@ -1,27 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Steps for Automatic SSL with Statping
|
||||
#
|
||||
# 1. Create EC2 Instance while including this file has "--data-file"
|
||||
# 2. Once EC2 is created, view System Logs in EC2 instance.
|
||||
# 3. In System logs, scroll all the way down and you'll see useful DNS records to use
|
||||
# 4. Edit your domains DNS to point to the EC2 server.
|
||||
#
|
||||
## Domain for new SSL certificate and email for Lets Encrypt SSL recovery
|
||||
|
||||
LETSENCRYPT_HOST="status.MYDOMAIN.com"
|
||||
LETSENCRYPT_EMAIL="noreply@MYEMAIL.com"
|
||||
|
||||
###################################################
|
||||
############# Leave this part alone ###############
|
||||
###################################################
|
||||
|
||||
exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
|
||||
printf "Statping will try to create an SSL for domain: $LETSENCRYPT_HOST\n"
|
||||
printf "\nexport LETSENCRYPT_HOST=$LETSENCRYPT_HOST\n" >> /home/ubuntu/.profile
|
||||
printf "export LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL\n" >> /home/ubuntu/.profile
|
||||
sudo printf "\nexport LETSENCRYPT_HOST=$LETSENCRYPT_HOST\n" >> /root/.profile
|
||||
sudo printf "export LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL\n" >> /root/.profile
|
||||
source /home/ubuntu/.profile
|
||||
sudo /bin/bash -c "source /root/.profile"
|
||||
LETSENCRYPT_HOST=$LETSENCRYPT_HOST LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL /home/ubuntu/init.sh
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'Prometheus'
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
|
@ -0,0 +1,50 @@
|
|||
# config file version
|
||||
apiVersion: 1
|
||||
|
||||
# list of datasources that should be deleted from the database
|
||||
deleteDatasources:
|
||||
- name: Prometheus
|
||||
orgId: 1
|
||||
|
||||
# list of datasources to insert/update depending
|
||||
# whats available in the database
|
||||
datasources:
|
||||
# <string, required> name of the datasource. Required
|
||||
- name: Prometheus
|
||||
# <string, required> datasource type. Required
|
||||
type: prometheus
|
||||
# <string, required> access mode. direct or proxy. Required
|
||||
access: proxy
|
||||
# <int> org id. will default to orgId 1 if not specified
|
||||
orgId: 1
|
||||
# <string> url
|
||||
url: http://prometheus:9090
|
||||
# <string> database password, if used
|
||||
password:
|
||||
# <string> database user, if used
|
||||
user:
|
||||
# <string> database name, if used
|
||||
database:
|
||||
# <bool> enable/disable basic auth
|
||||
basicAuth: false
|
||||
# <string> basic auth username, if used
|
||||
basicAuthUser:
|
||||
# <string> basic auth password, if used
|
||||
basicAuthPassword:
|
||||
# <bool> enable/disable with credentials headers
|
||||
withCredentials:
|
||||
# <bool> mark as default datasource. Max one per org
|
||||
isDefault: true
|
||||
# <map> fields that will be converted to json and stored in json_data
|
||||
jsonData:
|
||||
graphiteVersion: "1.1"
|
||||
tlsAuth: false
|
||||
tlsAuthWithCACert: false
|
||||
# <string> json object of data that will be encrypted.
|
||||
secureJsonData:
|
||||
tlsCACert: "..."
|
||||
tlsClientCert: "..."
|
||||
tlsClientKey: "..."
|
||||
version: 1
|
||||
# <bool> allow users to edit datasources from the UI.
|
||||
editable: true
|
|
@ -0,0 +1,7 @@
|
|||
[users]
|
||||
# disable user signup / registration
|
||||
allow_sign_up = false
|
||||
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
enabled = true
|
|
@ -0,0 +1,616 @@
|
|||
{
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_PROMETHEUS",
|
||||
"label": "prometheus",
|
||||
"description": "Required Prometheus endpoint",
|
||||
"type": "datasource",
|
||||
"pluginId": "prometheus",
|
||||
"pluginName": "Prometheus"
|
||||
}
|
||||
],
|
||||
"__requires": [
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "5.2.1"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "graph",
|
||||
"name": "Graph",
|
||||
"version": "5.0.0"
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
"id": "prometheus",
|
||||
"name": "Prometheus",
|
||||
"version": "5.0.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "singlestat",
|
||||
"name": "Singlestat",
|
||||
"version": "5.0.0"
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": 6950,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1531455265624,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorValue": true,
|
||||
"colors": [
|
||||
"#890f02",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgb(72, 198, 35)"
|
||||
],
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"format": "locale",
|
||||
"gauge": {
|
||||
"maxValue": 1,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 2,
|
||||
"w": 5,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": false,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "statup_service_online{name=\"${service}\"}",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": "0,1",
|
||||
"title": "",
|
||||
"transparent": true,
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "120%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "ONLINE",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"op": "=",
|
||||
"text": "OFFLINE",
|
||||
"value": "0"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fill": 1,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 19,
|
||||
"x": 5,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": false,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "statup_service_latency{name=\"${service}\"}",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "${service} Latency",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "ms",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorValue": false,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#890f02"
|
||||
],
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"decimals": 0,
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 500,
|
||||
"minValue": 1,
|
||||
"show": true,
|
||||
"thresholdLabels": true,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 5,
|
||||
"x": 0,
|
||||
"y": 2
|
||||
},
|
||||
"id": 7,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": false,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "statup_service_latency{name=\"${service}\"}",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": "250,400",
|
||||
"title": "",
|
||||
"transparent": true,
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "50%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fill": 1,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 19,
|
||||
"x": 5,
|
||||
"y": 8
|
||||
},
|
||||
"id": 8,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": false,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "statup_service_response_length{name=\"${service}\"}",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "${service} Response Size",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "bytes",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorValue": false,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#d44a3a"
|
||||
],
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"format": "locale",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 5,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 5,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": false,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "statup_service_status_code{name=\"${service}\"}",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": "",
|
||||
"title": "Response Status Code",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "120%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorValue": false,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#d44a3a"
|
||||
],
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"format": "bytes",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 5,
|
||||
"x": 0,
|
||||
"y": 12
|
||||
},
|
||||
"id": 4,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": false,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "statup_service_response_length{name=\"${service}\"}",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": "",
|
||||
"title": "Response Size",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 16,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"allValue": null,
|
||||
"current": {},
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "service",
|
||||
"multi": false,
|
||||
"name": "service",
|
||||
"options": [],
|
||||
"query": "label_values(statup_service_latency, name)",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "Statping",
|
||||
"uid": "6BzRjddmz",
|
||||
"version": 25,
|
||||
"description": "Monitor your websites and applications using Statping service. View more information at https://github.com/hunterlong/statping"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
**/*.go {
|
||||
prep: go build -o statping ./cmd
|
||||
daemon: ./statping --port=8585
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
worker_processes 1;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
server {
|
||||
location / {
|
||||
proxy_pass http://statping:8080;
|
||||
}
|
||||
listen 80;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
# Deploy Portainer demo (demo.portainer.io) in a play-with-docker playground
|
||||
#
|
||||
# - Go to http://play-with-docker.com/?stack=https://raw.githubusercontent.com/portainer/portainer-demo/master/play-with-docker/docker-stack.yml
|
||||
# - Login and/or Start.
|
||||
# - Wait until 'Your session is ready!' and 'Close' modal.
|
||||
# - Refresh (if a shell is not shown).
|
||||
# - Wait until a link with text '80' is shown. It is the link to the demo.
|
||||
# - Visit https://github.com/portainer/portainer to check default credentials.
|
||||
#
|
||||
version: '3'
|
||||
services:
|
||||
trigger:
|
||||
image: franela/dind
|
||||
command: sh -c "git clone https://github.com/portainer/portainer-demo/ && ./portainer-demo/portainer-demo.sh && tail -f /dev/null"
|
||||
volumes: [ '/var/run/docker.sock:/var/run/docker.sock' ]
|
|
@ -1,40 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PluginObj types.PluginInfo
|
||||
|
||||
var Plugin = PluginObj{
|
||||
Info: &types.Info{
|
||||
Name: "Example Plugin",
|
||||
Description: "This is an example plugin for Statping Status Page application. It can be implemented pretty quick!",
|
||||
},
|
||||
Routes: []*types.PluginRoute{{
|
||||
Url: "/setuper",
|
||||
Method: "GET",
|
||||
Func: SampleHandler,
|
||||
}},
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
||||
|
||||
func SampleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func (e *PluginObj) OnLoad() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PluginObj) GetInfo() *types.Info {
|
||||
return e.Info
|
||||
}
|
||||
|
||||
func (e *PluginObj) Router() []*types.PluginRoute {
|
||||
return e.Routes
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
|||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'statping_local'
|
||||
scrape_interval: 15s
|
||||
bearer_token: 'samplesecret'
|
||||
static_configs:
|
||||
- targets: ['docker0:8585']
|
||||
|
||||
- job_name: 'statping'
|
||||
scrape_interval: 15s
|
||||
bearer_token: 'exampleapisecret'
|
||||
static_configs:
|
||||
- targets: ['statping:8080']
|
||||
|
||||
- job_name: 'statping_mysql'
|
||||
scrape_interval: 15s
|
||||
bearer_token: 'exampleapisecret'
|
||||
static_configs:
|
||||
- targets: ['statping_mysql:8080']
|
||||
|
||||
- job_name: 'statping_postgres'
|
||||
scrape_interval: 15s
|
||||
bearer_token: 'exampleapisecret'
|
||||
static_configs:
|
||||
- targets: ['statping_postgres:8080']
|
||||
|
||||
- job_name: 'statping_dev'
|
||||
scrape_interval: 15s
|
||||
bearer_token: 'exampleapisecret'
|
||||
static_configs:
|
||||
- targets: ['statping_dev:8585']
|
|
@ -0,0 +1,37 @@
|
|||
https://demo.statping.com/
|
||||
https://github.com/hunterlong/statping
|
||||
https://statping.com
|
||||
https://hub.docker.com/r/hunterlong/statping/
|
||||
https://godoc.org/github.com/hunterlong/statping
|
||||
https://hub.docker.com/r/hunterlong/statping/
|
||||
https://goreportcard.com/report/github.com/hunterlong/statping
|
||||
http://slack.statping.com
|
||||
https://www.google.com/search?q=do+a+barrel+roll
|
||||
https://travis-ci.com/hunterlong/statping
|
||||
https://vue.statping.com
|
||||
http://gorm.io
|
||||
https://www.youtube.com/watch?v=mLOdar3jshI
|
||||
https://www.youtube.com/watch?v=WwoGhpYdebQ&t=1m11s
|
||||
https://www.youtube.com/watch?v=RPS-Cq4uMFs
|
||||
https://www.youtube.com/watch?v=5Ao5mg11xIk&t=1m9s
|
||||
https://www.youtube.com/watch?v=3tJ_SZDbPwQ
|
||||
https://www.youtube.com/watch?v=6IgaDHRiujA
|
||||
https://www.youtube.com/watch?v=x8eDyCRa0mY
|
||||
https://www.youtube.com/watch?v=N3kkNfH4yco
|
||||
tcp://8.8.8.8:53
|
||||
tcp://8.8.4.4:53
|
||||
https://mysql.statping.com
|
||||
https://postgres.statping.com
|
||||
tcp://hosted3.nfoservers.com:21
|
||||
tcp://hosted3.nfoservers.com:22
|
||||
tcp://hosted3.nfoservers.com:443
|
||||
tcp://hosted3.nfoservers.com:3306
|
||||
tcp://hosted15.nfoservers.com:3306
|
||||
tcp://hosted15.nfoservers.com:21
|
||||
tcp://hosted15.nfoservers.com:22
|
||||
tcp://hosted15.nfoservers.com:80
|
||||
tcp://hosted15.nfoservers.com:443
|
||||
tcp://demo.statping.com:80
|
||||
tcp://demo.statping.com:443
|
||||
tcp://facebook.com:80
|
||||
tcp://facebook.com:443
|
4
doc.go
4
doc.go
|
@ -1,10 +1,10 @@
|
|||
// Package statping is a server monitoring application that includes a status page server. Visit the Statping repo at
|
||||
// https://github.com/hunterlong/statping to get a full understanding of what this application can do.
|
||||
// https://github.com/statping/statping to get a full understanding of what this application can do.
|
||||
//
|
||||
// Install Statping
|
||||
//
|
||||
// Statping is available for Mac, Linux and Windows 64x. You can download the tar.gz file or use a couple other methods. Download
|
||||
// the latest release at https://github.com/hunterlong/statping/releases/latest or view below. If you're on windows, download
|
||||
// the latest release at https://github.com/statping/statping/releases/latest or view below. If you're on windows, download
|
||||
// the zip file from the latest releases link.
|
||||
//
|
||||
// // MacOS using homebrew
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./statping:/app
|
||||
environment:
|
||||
DB_CONN: sqlite
|
||||
version: '2.3'
|
||||
|
||||
services:
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- statping_data:/app
|
||||
environment:
|
||||
DB_CONN: sqlite
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8", "ie >= 11"]
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint",
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/base",
|
||||
"plugin:vue/essential",
|
||||
"plugin:vue/strongly-recommended",
|
||||
"plugin:vue/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"array-bracket-spacing": [ "error", "always" ],
|
||||
"arrow-spacing": "error",
|
||||
"constructor-super": "error",
|
||||
"curly": "error",
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"keyword-spacing": "error",
|
||||
"no-console": 0,
|
||||
"no-const-assign": "error",
|
||||
"no-debugger": 0,
|
||||
"no-duplicate-imports": "error",
|
||||
"no-multiple-empty-lines": [ 2, { "max": 2, "maxEOF": 1 } ],
|
||||
"no-this-before-super": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unused-vars": 0,
|
||||
"no-useless-escape": 0,
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": [ "error", "always" ],
|
||||
"one-var": ["error", { "var": "never", "let": "never", "const": "never" }],
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"quotes": [ "error", "single", { "allowTemplateLiterals": true } ],
|
||||
"semi": ["error", "always"],
|
||||
"sort-imports": ["error", { "ignoreDeclarationSort": true }],
|
||||
"space-before-function-paren": "error",
|
||||
"valid-typeof": "error",
|
||||
"vue/component-name-in-template-casing": ["error", "PascalCase",
|
||||
{
|
||||
"ignores": [
|
||||
"router-link",
|
||||
"router-view",
|
||||
"transition"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"vue"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "stylelint-config-sass-guidelines"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
NODE_ENV: 'development'
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const _root = path.resolve(__dirname, '..');
|
||||
|
||||
exports.root = function (args) {
|
||||
args = Array.prototype.slice.call(arguments, 0);
|
||||
|
||||
return path.join.apply(path, [ _root ].concat(args));
|
||||
};
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
return path.posix.join('assets', _path);
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
NODE_ENV: 'production'
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
[defaults]
|
||||
org=Statping
|
||||
project=statping_frontend
|
||||
url=https://sentry.statping.com
|
||||
|
||||
[auth]
|
||||
token=6b5b07262307407daef8fc9606954dfd56453f9fed2e4bb5b494ab1753bbcac0
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue