mirror of https://github.com/statping/statping
commit
2a4c3d6f8f
|
|
@ -1,2 +1,3 @@
|
||||||
github: hunterlong
|
github: hunterlong
|
||||||
|
patreon: statping
|
||||||
custom: ['https://www.buymeacoffee.com/hunterlong']
|
custom: ['https://www.buymeacoffee.com/hunterlong']
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ snap
|
||||||
prime
|
prime
|
||||||
stage
|
stage
|
||||||
parts
|
parts
|
||||||
|
releases
|
||||||
core/rice-box.go
|
core/rice-box.go
|
||||||
config.yml
|
config.yml
|
||||||
*.db
|
*.db
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ before_script:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
- dev
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- "PATH=$HOME/.local/bin:$PATH"
|
- "PATH=$HOME/.local/bin:$PATH"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
# 0.90.26 (04-13-2020)
|
||||||
|
- Fixed Delete Failures button/function
|
||||||
|
- Removed timezone field from Settings (core)
|
||||||
|
- Modified CDN asset URL
|
||||||
|
- Fixed single Service view, more complex charts
|
||||||
|
|
||||||
# 0.90.25
|
# 0.90.25
|
||||||
- Added string response on OnTest for Notifiers
|
- Added string response on OnTest for Notifiers
|
||||||
- Modified UI to show user the response for a Notifier.
|
- Modified UI to show user the response for a Notifier.
|
||||||
|
|
|
||||||
123
Makefile
123
Makefile
|
|
@ -7,11 +7,13 @@ XGO=xgo -go $(GOVERSION) --dest=build
|
||||||
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)"
|
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)"
|
||||||
TRVIS_SECRET=O3/2KTOV8krv+yZ1EB/7D1RQRe6NdpFUEJNJkMS/ollYqmz3x2mCO7yIgIJKCKguLXZxjM6CxJcjlCrvUwibL+8BBp7xJe4XFIOrjkPvbbVPry4HkFZCf2GfcUK6o4AByQ+RYqsW2F17Fp9KLQ1rL3OT3eLTwCAGKx3tlY8y+an43zkmo5dN64V6sawx26fh6XTfww590ey+ltgQTjf8UPNup2wZmGvMo9Hwvh/bYR/47bR6PlBh6vhlKWyotKf2Fz1Bevbu0zc35pee5YlsrHR+oSF+/nNd/dOij34BhtqQikUR+zQVy9yty8SlmneVwD3yOENvlF+8roeKIXb6P6eZnSMHvelhWpAFTwDXq2N3d/FIgrQtLxsAFTI3nTHvZgs6OoTd6dA0wkhuIGLxaL3FOeztCdxP5J/CQ9GUcTvifh5ArGGwYxRxQU6rTgtebJcNtXFISP9CEUR6rwRtb6ax7h6f1SbjUGAdxt+r2LbEVEk4ZlwHvdJ2DtzJHT5DQtLrqq/CTUgJ8SJFMkrJMp/pPznKhzN4qvd8oQJXygSXX/gz92MvoX0xgpNeLsUdAn+PL9KketfR+QYosBz04d8k05E+aTqGaU7FUCHPTLwlOFvLD8Gbv0zsC/PWgSLXTBlcqLEz5PHwPVHTcVzspKj/IyYimXpCSbvu1YOIjyc=
|
TRVIS_SECRET=O3/2KTOV8krv+yZ1EB/7D1RQRe6NdpFUEJNJkMS/ollYqmz3x2mCO7yIgIJKCKguLXZxjM6CxJcjlCrvUwibL+8BBp7xJe4XFIOrjkPvbbVPry4HkFZCf2GfcUK6o4AByQ+RYqsW2F17Fp9KLQ1rL3OT3eLTwCAGKx3tlY8y+an43zkmo5dN64V6sawx26fh6XTfww590ey+ltgQTjf8UPNup2wZmGvMo9Hwvh/bYR/47bR6PlBh6vhlKWyotKf2Fz1Bevbu0zc35pee5YlsrHR+oSF+/nNd/dOij34BhtqQikUR+zQVy9yty8SlmneVwD3yOENvlF+8roeKIXb6P6eZnSMHvelhWpAFTwDXq2N3d/FIgrQtLxsAFTI3nTHvZgs6OoTd6dA0wkhuIGLxaL3FOeztCdxP5J/CQ9GUcTvifh5ArGGwYxRxQU6rTgtebJcNtXFISP9CEUR6rwRtb6ax7h6f1SbjUGAdxt+r2LbEVEk4ZlwHvdJ2DtzJHT5DQtLrqq/CTUgJ8SJFMkrJMp/pPznKhzN4qvd8oQJXygSXX/gz92MvoX0xgpNeLsUdAn+PL9KketfR+QYosBz04d8k05E+aTqGaU7FUCHPTLwlOFvLD8Gbv0zsC/PWgSLXTBlcqLEz5PHwPVHTcVzspKj/IyYimXpCSbvu1YOIjyc=
|
||||||
PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }'
|
PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }'
|
||||||
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "merge_mode": "replace", "language": "go", "install": true, "sudo": "required", "services": ["docker"], "env": { "secure": "${TRVIS_SECRET}" }, "before_deploy": ["git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [{ "provider": "releases", "api_key": "$$GITHUB_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": { "branch": "master" } }], "before_script": ["rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install stable", "nvm install 10.17.0", "nvm use 10.17.0 --default", "npm install -g sass", "npm install -g cross-env"], "script": ["travis_wait 30 docker pull crazymax/xgo:${GOVERSION}", "make release"], "after_success": [], "after_deploy": ["make publish-homebrew"] } } }'
|
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "merge_mode": "replace", "language": "go", "install": true, "sudo": "required", "services": ["docker"], "env": { "secure": "${TRVIS_SECRET}" }, "before_deploy": ["git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [{ "provider": "releases", "api_key": "$$GITHUB_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": { "branch": "master" } }], "before_script": ["rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install stable", "nvm install 10.17.0", "nvm use 10.17.0 --default", "npm install -g sass yarn cross-env", "pip install --user awscli"], "script": ["make release"], "after_success": [], "after_deploy": ["make post-release"] } } }'
|
||||||
TEST_DIR=$(GOPATH)/src/github.com/statping/statping
|
TEST_DIR=$(GOPATH)/src/github.com/statping/statping
|
||||||
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
|
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
|
||||||
|
OS = darwin freebsd linux openbsd
|
||||||
|
ARCHS = 386 arm amd64 arm64
|
||||||
|
|
||||||
all: clean yarn-install compile docker-base docker-vue build-all compress
|
all: clean yarn-install compile docker-base docker-vue build-all
|
||||||
|
|
||||||
up:
|
up:
|
||||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans
|
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans
|
||||||
|
|
@ -27,8 +29,8 @@ lite: clean
|
||||||
|
|
||||||
reup: down clean compose-build-full up
|
reup: down clean compose-build-full up
|
||||||
|
|
||||||
test: clean
|
test: clean compile
|
||||||
go test -v -p=4 -ldflags="-X main.VERSION=testing" -coverprofile=coverage.out ./...
|
go test -v -p=1 -ldflags="-X main.VERSION=testing" -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
# build all arch's and release Statping
|
# build all arch's and release Statping
|
||||||
release: test-deps
|
release: test-deps
|
||||||
|
|
@ -55,6 +57,9 @@ test-deps:
|
||||||
go get github.com/mattn/goveralls
|
go get github.com/mattn/goveralls
|
||||||
go get github.com/GeertJohan/go.rice/rice
|
go get github.com/GeertJohan/go.rice/rice
|
||||||
|
|
||||||
|
deps:
|
||||||
|
go get -d -v -t ./...
|
||||||
|
|
||||||
protoc:
|
protoc:
|
||||||
cd types/proto && protoc --gofast_out=plugins=grpc:. statping.proto
|
cd types/proto && protoc --gofast_out=plugins=grpc:. statping.proto
|
||||||
|
|
||||||
|
|
@ -146,6 +151,34 @@ install-local: build
|
||||||
generate:
|
generate:
|
||||||
cd source && go generate
|
cd source && go generate
|
||||||
|
|
||||||
|
build-bin:
|
||||||
|
mkdir build
|
||||||
|
export PWD=`pwd`
|
||||||
|
@for arch in $(ARCHS);\
|
||||||
|
do \
|
||||||
|
for os in $(OS);\
|
||||||
|
do \
|
||||||
|
echo "Building $$os-$$arch"; \
|
||||||
|
mkdir -p releases/statping-$$os-$$arch/; \
|
||||||
|
GOOS=$$os GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-$$os-$$arch/statping ${PWD}/cmd; \
|
||||||
|
chmod +x releases/statping-$$os-$$arch/statping; \
|
||||||
|
tar -czf releases/statping-$$os-$$arch.tar.gz -C releases/statping-$$os-$$arch statping; \
|
||||||
|
done \
|
||||||
|
done
|
||||||
|
find ./releases/ -name "*.tar.gz" -type f -size +1M -exec mv "{}" build/ \;
|
||||||
|
|
||||||
|
build-win:
|
||||||
|
export PWD=`pwd`
|
||||||
|
@for arch in $(ARCHS);\
|
||||||
|
do \
|
||||||
|
echo "Building windows-$$arch"; \
|
||||||
|
mkdir -p releases/statping-windows-$$arch/; \
|
||||||
|
GOOS=windows GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-windows-$$arch/statping.exe ${PWD}/cmd; \
|
||||||
|
chmod +x releases/statping-windows-$$arch/statping.exe; \
|
||||||
|
zip -j releases/statping-windows-$$arch.zip releases/statping-windows-$$arch/statping.exe || true; \
|
||||||
|
done
|
||||||
|
find ./releases/ -name "*.zip" -type f -size +1M -exec mv "{}" build/ \;
|
||||||
|
|
||||||
# remove files for a clean compile/build
|
# remove files for a clean compile/build
|
||||||
clean:
|
clean:
|
||||||
rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor}
|
rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor}
|
||||||
|
|
@ -168,7 +201,7 @@ clean:
|
||||||
find . -name "*.out" -type f -delete
|
find . -name "*.out" -type f -delete
|
||||||
find . -name "*.cpu" -type f -delete
|
find . -name "*.cpu" -type f -delete
|
||||||
find . -name "*.mem" -type f -delete
|
find . -name "*.mem" -type f -delete
|
||||||
rm -rf {build,tmp}
|
rm -rf {build,releases,tmp}
|
||||||
|
|
||||||
print_details:
|
print_details:
|
||||||
@echo \==== Statping Development Instance ====
|
@echo \==== Statping Development Instance ====
|
||||||
|
|
@ -186,7 +219,7 @@ print_details:
|
||||||
@echo \==== Monitoring and IDE ====
|
@echo \==== Monitoring and IDE ====
|
||||||
@echo \Grafana: http://localhost:3000 \(username: admin, password: admin\)
|
@echo \Grafana: http://localhost:3000 \(username: admin, password: admin\)
|
||||||
|
|
||||||
build-all: xgo-install compile build-mac build-linux build-windows build-linux build-alpine compress
|
build-all: clean compile build-bin build-win
|
||||||
|
|
||||||
coverage: test-deps
|
coverage: test-deps
|
||||||
$(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS)
|
$(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS)
|
||||||
|
|
@ -200,62 +233,23 @@ download-key:
|
||||||
wget -O statping.gpg $(SIGN_URL)
|
wget -O statping.gpg $(SIGN_URL)
|
||||||
gpg --import statping.gpg
|
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
|
# build :latest docker tag
|
||||||
docker-build-latest:
|
docker-build-latest:
|
||||||
docker build --build-arg VERSION=${VERSION} -t statping/statping:latest --no-cache -f Dockerfile .
|
docker build --build-arg VERSION=${VERSION} -t statping/statping:latest --no-cache -f Dockerfile .
|
||||||
docker tag statping/statping:latest statping/statping:v${VERSION}
|
docker tag statping/statping:latest statping/statping:v${VERSION}
|
||||||
|
|
||||||
# compress built binaries into tar.gz and zip formats
|
|
||||||
compress:
|
|
||||||
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
|
|
||||||
cd build && mv cmd-darwin-10.6-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)-osx-x64.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
cd build && mv cmd-darwin-10.6-386 $(BINARY_NAME)
|
|
||||||
cd build && gpg --default-key $(SIGN_KEY) --batch --detach-sign --output statping.asc --armor $(BINARY_NAME)
|
|
||||||
cd build && tar -czvf $(BINARY_NAME)-osx-x32.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
cd build && mv cmd-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-x64.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
cd build && mv cmd-linux-386 $(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-x32.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
cd build && mv cmd-windows-6.0-amd64.exe $(BINARY_NAME).exe
|
|
||||||
cd build && gpg --default-key $(SIGN_KEY) --batch --detach-sign --output statping.asc --armor $(BINARY_NAME).exe
|
|
||||||
cd build && zip $(BINARY_NAME)-windows-x64.zip $(BINARY_NAME).exe statping.asc && rm -f $(BINARY_NAME).exe statping.asc
|
|
||||||
cd build && mv cmd-linux-arm-7 $(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-arm7.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
cd build && mv cmd-linux-arm-6 $(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-arm6.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
cd build && mv cmd-linux-arm64 $(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-arm64.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc
|
|
||||||
|
|
||||||
# push the :dev docker tag using curl
|
# push the :dev docker tag using curl
|
||||||
publish-dev:
|
publish-dev:
|
||||||
curl -H "Content-Type: application/json" --data '{"docker_tag": "dev"}' -X POST $(DOCKER)
|
curl -H "Content-Type: application/json" --data '{"docker_tag": "dev"}' -X POST $(DOCKER)
|
||||||
|
|
||||||
|
publish-latest: publish-base
|
||||||
|
curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER)
|
||||||
|
|
||||||
|
publish-base:
|
||||||
|
curl -H "Content-Type: application/json" --data '{"docker_tag": "base"}' -X POST $(DOCKER)
|
||||||
|
|
||||||
|
post-release: frontend-build upload_to_s3 publish-homebrew publish-latest
|
||||||
|
|
||||||
# update the homebrew application to latest for mac
|
# update the homebrew application to latest for mac
|
||||||
publish-homebrew:
|
publish-homebrew:
|
||||||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/statping%2Fhomebrew-statping/requests
|
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/statping%2Fhomebrew-statping/requests
|
||||||
|
|
@ -286,5 +280,28 @@ sentry-release:
|
||||||
sentry-cli releases set-commits --auto v${VERSION}
|
sentry-cli releases set-commits --auto v${VERSION}
|
||||||
sentry-cli releases finalize v${VERSION}
|
sentry-cli releases finalize v${VERSION}
|
||||||
|
|
||||||
|
snapcraft: clean snapcraft-build snapcraft-release
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release
|
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release
|
||||||
.SILENT: travis_s3_creds
|
.SILENT: travis_s3_creds
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,45 @@ type isObject interface {
|
||||||
Db() Database
|
Db() Database
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseRequest(r *http.Request) (*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
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Start.After(query.End) {
|
||||||
|
return nil, errors.New("start time is after ending time")
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
||||||
fields := parseGet(r)
|
fields := parseGet(r)
|
||||||
grouping := fields.Get("group")
|
grouping := fields.Get("group")
|
||||||
|
|
@ -169,6 +208,9 @@ func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
groupDur = 1 * time.Hour
|
groupDur = 1 * time.Hour
|
||||||
}
|
}
|
||||||
|
if endField == 0 {
|
||||||
|
endField = utils.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
query := &GroupQuery{
|
query := &GroupQuery{
|
||||||
Start: time.Unix(startField, 0).UTC(),
|
Start: time.Unix(startField, 0).UTC(),
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
<base href="{{BasePath}}">
|
<base href="{{BasePath}}">
|
||||||
{{if USE_CDN}}
|
{{if USE_CDN}}
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/favicon.ico">
|
||||||
<link rel="stylesheet" href="https://assets.statping.com/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://assets.statping.com/vendor.css">
|
||||||
<link rel="stylesheet" href="https://assets.statping.com/css/base.css">
|
<link rel="stylesheet" href="https://assets.statping.com/style.css">
|
||||||
<link rel="stylesheet" href="https://assets.statping.com/font/all.css">
|
<link rel="stylesheet" href="https://assets.statping.com/main.css">
|
||||||
{{else}}
|
{{else}}
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||||
{{if USING_ASSETS}}
|
{{if USING_ASSETS}}
|
||||||
|
|
@ -33,11 +33,11 @@
|
||||||
<div id="app" class="statping_container"></div>
|
<div id="app" class="statping_container"></div>
|
||||||
|
|
||||||
{{if USE_CDN}}
|
{{if USE_CDN}}
|
||||||
<script src="https://assets.statping.com/js/bundle.js"></script>
|
<script src="https://assets.statping.com/bundle.js"></script>
|
||||||
<script src="https://assets.statping.com/js/vendor.chunk.js"></script>
|
<script src="https://assets.statping.com/vendor.chunk.js"></script>
|
||||||
<script src="https://assets.statping.com/js/polyfill.chunk.js"></script>
|
<script src="https://assets.statping.com/polyfill.chunk.js"></script>
|
||||||
<script src="https://assets.statping.com/js/style.chunk.js"></script>
|
<script src="https://assets.statping.com/style.chunk.js"></script>
|
||||||
<script src="https://assets.statping.com/js/main.chunk.js"></script>
|
<script src="https://assets.statping.com/main.chunk.js"></script>
|
||||||
{{else}}
|
{{else}}
|
||||||
<% _.each(htmlWebpackPlugin.tags.bodyTags, function(bodyTag) { %>
|
<% _.each(htmlWebpackPlugin.tags.bodyTags, function(bodyTag) { %>
|
||||||
<%= bodyTag %> <% }) %>
|
<%= bodyTag %> <% }) %>
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ class Api {
|
||||||
return axios.get('api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
return axios.get('api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_uptime(id) {
|
async service_uptime(id, start, end) {
|
||||||
return axios.get('api/services/' + id + '/uptime_data').then(response => (response.data))
|
return axios.get('api/services/' + id + '/uptime_data?start=' + start + '&end=' + end).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_heatmap(id, start, end, group) {
|
async service_heatmap(id, start, end, group) {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Api from "../API";
|
import Api from "../../API";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Checkins',
|
name: 'Checkins',
|
||||||
|
|
@ -36,10 +36,12 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div v-if="$store.state.admin" class="btn-group">
|
<div v-if="$store.state.admin" class="btn-group">
|
||||||
<a @click.prevent="editGroup(group, edit)" href="#" class="btn btn-outline-secondary"><font-awesome-icon icon="chart-area" /> Edit</a>
|
<button @click.prevent="editGroup(group, edit)" href="#" class="btn btn-sm btn-outline-secondary">
|
||||||
<a @click.prevent="deleteGroup(group)" href="#" class="btn btn-danger">
|
<font-awesome-icon icon="edit" />
|
||||||
|
</button>
|
||||||
|
<button @click.prevent="deleteGroup(group)" href="#" class="btn btn-sm btn-danger">
|
||||||
<font-awesome-icon icon="times" />
|
<font-awesome-icon icon="times" />
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h2>{{service.name}} Failures
|
<h2>{{service.name}} Failures
|
||||||
<span class="btn btn-outline-danger float-right">Delete All</span></h2>
|
<button v-if="failures.length>0" @click="deleteFailures" class="btn btn-outline-danger float-right">Delete All</button></h2>
|
||||||
<div class="list-group mt-3 mb-4">
|
<div class="list-group mt-3 mb-4">
|
||||||
|
|
||||||
|
<div class="alert alert-info" v-if="failures.length===0">
|
||||||
|
You don't have any failures for {{service.name}}. Way to go!
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-for="(failure, index) in failures" :key="index" class="mb-2 list-group-item list-group-item-action flex-column align-items-start">
|
<div v-for="(failure, index) in failures" :key="index" class="mb-2 list-group-item list-group-item-action flex-column align-items-start">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">{{failure.issue}}</h5>
|
<h5 class="mb-1">{{failure.issue}}</h5>
|
||||||
|
|
@ -39,7 +43,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Api from "../API";
|
import Api from "../../API";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Failures',
|
name: 'Failures',
|
||||||
|
|
@ -72,13 +76,21 @@ export default {
|
||||||
await this.gotoPage(1)
|
await this.gotoPage(1)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async deleteFailures() {
|
||||||
|
const c = confirm('Are you sure you want to delete all failures?')
|
||||||
|
if (c) {
|
||||||
|
await Api.service_failures_delete(this.service)
|
||||||
|
this.service = await Api.service(this.service.id)
|
||||||
|
this.total = 0
|
||||||
|
await this.load()
|
||||||
|
}
|
||||||
|
},
|
||||||
async gotoPage(page) {
|
async gotoPage(page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
|
|
||||||
this.offset = (page-1) * this.limit;
|
this.offset = (page-1) * this.limit;
|
||||||
|
await this.load()
|
||||||
window.console.log('page', this.page, this.limit, this.offset);
|
},
|
||||||
|
async load() {
|
||||||
this.failures = await Api.service_failures(this.service.id, 0, 9999999999, this.limit, this.offset)
|
this.failures = await Api.service_failures(this.service.id, 0, 9999999999, this.limit, this.offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Api from "../API";
|
import Api from "../../API";
|
||||||
import FormIncidentUpdates from "@/forms/IncidentUpdates";
|
import FormIncidentUpdates from "@/forms/IncidentUpdates";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div v-for="(incident, i) in incidents" class="col-12 mt-4 mb-3">
|
<div v-for="(incident, i) in incidents" class="col-12 mt-4 mb-3">
|
||||||
<span class="braker mt-1 mb-3"></span>
|
<span class="braker mt-1 mb-3"></span>
|
||||||
<h6>Incident: {{incident.title}}<span class="font-2 float-right">{{niceDate(incident.created_at)}}</span></h6>
|
<h6>Incident: {{incident.title}}
|
||||||
|
<span class="font-2 float-right">{{niceDate(incident.created_at)}}</span>
|
||||||
|
</h6>
|
||||||
<span class="font-2" v-html="incident.description"></span>
|
<span class="font-2" v-html="incident.description"></span>
|
||||||
|
|
||||||
<UpdatesBlock :incident="incident"/>
|
<UpdatesBlock :incident="incident"/>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div v-for="(update, i) in updates" v-bind:key="i" class="col-12 mt-3">
|
<div v-for="(update, i) in updates" v-bind:key="i" class="col-12 mt-3">
|
||||||
<span class="col-2 badge text-uppercase" :class="badgeClass(update.type)">{{update.type}}</span>
|
<div class="col-md-2 col-12">
|
||||||
<span class="col-10">{{update.message}}</span>
|
<span class="badge text-uppercase" :class="badgeClass(update.type)">{{update.type}}</span>
|
||||||
<span class="col-12 font-1 float-right text-black-50">{{ago(update.created_at)}} ago</span>
|
</div>
|
||||||
|
<div class="col-md-12 col-12 mt-2 font-3">{{update.message}}</div>
|
||||||
|
<div class="col-12 font-1 float-right text-black-50 mt-2">{{ago(update.created_at)}} ago</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="service-chart-container">
|
||||||
|
<apexchart width="100%" height="420" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Api from "../../API";
|
||||||
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AdvancedChart',
|
||||||
|
props: {
|
||||||
|
service: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
updated: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
main_data: null,
|
||||||
|
expanded_data: null,
|
||||||
|
main_chart_options: {
|
||||||
|
noData: {
|
||||||
|
text: "Loading...",
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: -20,
|
||||||
|
style: {
|
||||||
|
color: "#bababa",
|
||||||
|
fontSize: '27px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
id: 'mainchart',
|
||||||
|
events: {
|
||||||
|
dataPointSelection: (event, chartContext, config) => {
|
||||||
|
window.console.log('slect')
|
||||||
|
window.console.log(event)
|
||||||
|
},
|
||||||
|
updated: (chartContext, config) => {
|
||||||
|
window.console.log('updated')
|
||||||
|
},
|
||||||
|
beforeZoom: (chartContext, { xaxis }) => {
|
||||||
|
const start = (xaxis.min / 1000).toFixed(0)
|
||||||
|
const end = (xaxis.max / 1000).toFixed(0)
|
||||||
|
window.console.log(start, end)
|
||||||
|
this.updated(this.fromUnix(start), this.fromUnix(end))
|
||||||
|
return {
|
||||||
|
xaxis: {
|
||||||
|
min: this.fromUnix(start),
|
||||||
|
max: this.fromUnix(end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrolled: (chartContext, { xaxis }) => {
|
||||||
|
window.console.log(xaxis)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
height: 500,
|
||||||
|
width: "100%",
|
||||||
|
type: "area",
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
initialAnimation: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
curve: 'stepline',
|
||||||
|
lineCap: 'butt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
labels: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
size: 0,
|
||||||
|
strokeWidth: 0,
|
||||||
|
hover: {
|
||||||
|
size: undefined,
|
||||||
|
sizeOffset: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: true,
|
||||||
|
custom: function ({ series, seriesIndex, dataPointIndex, w }) {
|
||||||
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
|
let val = series[seriesIndex][dataPointIndex];
|
||||||
|
if (val >= 1000) {
|
||||||
|
val = (val * 0.1).toFixed(0) + " milliseconds"
|
||||||
|
} else {
|
||||||
|
val = (val * 0.01).toFixed(0) + " microseconds"
|
||||||
|
}
|
||||||
|
return `<div class="chartmarker"><span>Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
|
||||||
|
},
|
||||||
|
fixed: {
|
||||||
|
enabled: true,
|
||||||
|
position: 'topRight',
|
||||||
|
offsetX: -30,
|
||||||
|
offsetY: 40,
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
formatter: undefined,
|
||||||
|
title: {
|
||||||
|
formatter: (seriesName) => seriesName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
floating: true,
|
||||||
|
axisTicks: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
axisBorder: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
colors: ["#48d338"],
|
||||||
|
opacity: 1,
|
||||||
|
type: 'solid'
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: true,
|
||||||
|
curve: 'smooth',
|
||||||
|
lineCap: 'butt',
|
||||||
|
colors: ["#3aa82d"],
|
||||||
|
width: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expanded_chart_options: {
|
||||||
|
chart: {
|
||||||
|
id: "chart1",
|
||||||
|
height: 130,
|
||||||
|
type: "bar",
|
||||||
|
foreColor: "#ccc",
|
||||||
|
brush: {
|
||||||
|
target: "chart2",
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
enabled: true,
|
||||||
|
fill: {
|
||||||
|
color: "#fff",
|
||||||
|
opacity: 0.4
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
min: new Date("27 Jul 2017 10:00:00").getTime(),
|
||||||
|
max: new Date("14 Aug 2999 10:00:00").getTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ["#FF0080"],
|
||||||
|
stroke: {
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: "#444"
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
size: 0
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.update_data();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
main_chart () {
|
||||||
|
return [{
|
||||||
|
name: this.service.name,
|
||||||
|
...this.convertToChartData(this.main_data)
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
expanded_chart () {
|
||||||
|
return this.toBarData(this.expanded_data)
|
||||||
|
},
|
||||||
|
params () {
|
||||||
|
return {start: this.toUnix(new Date(this.start)), end: this.toUnix(new Date(this.end))}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
start: function(n, o) {
|
||||||
|
this.update_data()
|
||||||
|
},
|
||||||
|
end: function(n, o) {
|
||||||
|
this.update_data()
|
||||||
|
},
|
||||||
|
group: function(n, o) {
|
||||||
|
this.update_data()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async update_data() {
|
||||||
|
this.loading = true
|
||||||
|
await this.chartHits()
|
||||||
|
// await this.expanded_hits()
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
async expanded_hits() {
|
||||||
|
this.expanded_data = await this.load_hits(0, 99999999999, "24h")
|
||||||
|
},
|
||||||
|
async chartHits() {
|
||||||
|
this.main_data = await this.load_hits()
|
||||||
|
},
|
||||||
|
async load_hits(start=this.params.start, end=this.params.end, group=this.group) {
|
||||||
|
return await Api.service_hits(this.service.id, start, end, group, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
|
|
@ -49,12 +49,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 col-6 float-right">
|
<div class="col-md-4 col-6 float-right">
|
||||||
<button v-if="!expanded" @click="showMoreStats" class="btn btn-sm float-right dyn-dark text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">View Service</button>
|
<button v-if="!expanded" @click="setService" class="btn btn-sm float-right dyn-dark text-white" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||||
<button v-if="expanded" @click="expanded = false" class="btn btn-sm float-right dyn-dark text-white" :class="{'btn-outline-success': service.online, 'bg-danger': !service.online}">Hide</button>
|
View Service
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div v-if="expanded" class="row">
|
|
||||||
<Analytics title="Last Failure" value="417 Days ago"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -133,6 +130,10 @@ export default {
|
||||||
this.track_service = this.in_service
|
this.track_service = this.in_service
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async setService() {
|
||||||
|
await this.$store.commit('setService', this.service)
|
||||||
|
this.$router.push('/service/'+this.service.id, {props: {in_service: this.service}})
|
||||||
|
},
|
||||||
async showMoreStats() {
|
async showMoreStats() {
|
||||||
this.expanded = !this.expanded;
|
this.expanded = !this.expanded;
|
||||||
|
|
||||||
|
|
@ -174,7 +175,6 @@ export default {
|
||||||
if (!this.timer_func) {
|
if (!this.timer_func) {
|
||||||
this.timer_func = setInterval(async () => {
|
this.timer_func = setInterval(async () => {
|
||||||
this.track_service = await Api.service(this.service.id)
|
this.track_service = await Api.service(this.service.id)
|
||||||
window.console.log(this.track_service.name)
|
|
||||||
}, this.track_service.check_interval * 1000)
|
}, this.track_service.check_interval * 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,23 +67,23 @@
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-3">
|
<div class="col-12 col-md-3 mb-2 mb-md-0">
|
||||||
<router-link :to="{path: `/dashboard/service/${service.id}/incidents`, params: {id: service.id} }" class="btn btn-block btn-white incident">
|
<router-link :to="{path: `/dashboard/service/${service.id}/incidents`, params: {id: service.id} }" class="btn btn-block btn-white incident">
|
||||||
Incidents
|
Incidents
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-12 col-md-3 mb-2 mb-md-0">
|
||||||
<router-link :to="{path: `/dashboard/service/${service.id}/checkins`, params: {id: service.id} }" class="btn btn-block btn-white checkins">
|
<router-link :to="{path: `/dashboard/service/${service.id}/checkins`, params: {id: service.id} }" class="btn btn-block btn-white checkins">
|
||||||
Checkins
|
Checkins
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-12 col-md-3 mb-2 mb-md-0">
|
||||||
<router-link :to="{path: `/dashboard/service/${service.id}/failures`, params: {id: service.id} }" class="btn btn-block btn-white failures">
|
<router-link :to="{path: `/dashboard/service/${service.id}/failures`, params: {id: service.id} }" class="btn btn-block btn-white failures">
|
||||||
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span>
|
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 pt-2">
|
<div class="col-12 col-md-3 mb-2 mb-md-0 mt-0 mt-md-1">
|
||||||
<span class="text-black-50 float-right">{{service.online_7_days}}% Uptime</span>
|
<span class="text-black-50 float-md-right">{{service.online_7_days}}% Uptime</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -155,13 +155,6 @@
|
||||||
this.set2 = await this.getHits(24, "1h")
|
this.set2 = await this.getHits(24, "1h")
|
||||||
this.set2_name = this.calc(this.set2)
|
this.set2_name = this.calc(this.set2)
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
},
|
|
||||||
async deleteFailures() {
|
|
||||||
const c = confirm('Are you sure you want to delete all failures?')
|
|
||||||
if (c) {
|
|
||||||
await Api.service_failures_delete(this.service)
|
|
||||||
this.service = await Api.service(this.service.id)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Tab(name) {
|
Tab(name) {
|
||||||
if (this.openTab === name) {
|
if (this.openTab === name) {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ export default Vue.mixin({
|
||||||
dur(t1, t2) {
|
dur(t1, t2) {
|
||||||
return formatDistance(t1, t2)
|
return formatDistance(t1, t2)
|
||||||
},
|
},
|
||||||
|
format(val, type="EEEE, MMM do h:mma") {
|
||||||
|
return format(val, type)
|
||||||
|
},
|
||||||
niceDate(val) {
|
niceDate(val) {
|
||||||
return format(parseISO(val), "EEEE, MMM do h:mma")
|
return format(parseISO(val), "EEEE, MMM do h:mma")
|
||||||
},
|
},
|
||||||
|
|
@ -119,6 +122,13 @@ export default Vue.mixin({
|
||||||
return "bars"
|
return "bars"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toBarData(data = []) {
|
||||||
|
let newSet = [];
|
||||||
|
data.forEach((f) => {
|
||||||
|
newSet.push([this.toUnix(this.parseISO(f.timeframe)), f.amount])
|
||||||
|
})
|
||||||
|
return newSet
|
||||||
|
},
|
||||||
convertToChartData(data = [], multiplier=1, asInt=false) {
|
convertToChartData(data = [], multiplier=1, asInt=false) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return {data: []}
|
return {data: []}
|
||||||
|
|
|
||||||
|
|
@ -21,69 +21,42 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mt-5 mb-4">
|
<div class="row mt-5 mb-4">
|
||||||
<span class="col-6 font-2">
|
<div class="col-12 col-md-5 font-2 mb-3 mb-md-0">
|
||||||
<flatPickr v-model="start_time" type="text" name="start_time" class="form-control form-control-plaintext" id="start_time" value="0001-01-01T00:00:00Z" required />
|
<flatPickr :disabled="loading" @on-change="onnn" v-model="start_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="btn btn-white text-left" required />
|
||||||
</span>
|
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
||||||
<span class="col-6 font-2">
|
</div>
|
||||||
<flatPickr v-model="end_time" type="text" name="end_time" class="form-control form-control-plaintext" id="end_time" value="0001-01-01T00:00:00Z" required />
|
<div class="col-12 col-md-5 font-2 mb-3 mb-md-0">
|
||||||
</span>
|
<flatPickr :disabled="loading" @on-change="onnn" v-model="end_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date()}" type="text" class="btn btn-white text-left" required />
|
||||||
|
<small class="d-block">To {{this.format(new Date(end_time))}}</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-2">
|
||||||
|
<select :disabled="loading" @change="chartHits" v-model="group" class="form-control">
|
||||||
|
<option value="1m">1 Minute</option>
|
||||||
|
<option value="5m">5 Minutes</option>
|
||||||
|
<option value="15m">15 Minute</option>
|
||||||
|
<option value="30m">30 Minutes</option>
|
||||||
|
<option value="1h">1 Hour</option>
|
||||||
|
<option value="3h">3 Hours</option>
|
||||||
|
<option value="6h">6 Hours</option>
|
||||||
|
<option value="12h">12 Hours</option>
|
||||||
|
<option value="24h">1 Day</option>
|
||||||
|
<option value="168h">7 Days</option>
|
||||||
|
<option value="360h">15 Days</option>
|
||||||
|
</select>
|
||||||
|
<small class="d-block d-md-none d-block">Increment Timeframe</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="series" class="service-chart-container">
|
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
||||||
<apexchart width="100%" height="420" type="area" :options="chartOptions" :series="series"></apexchart>
|
|
||||||
|
<div v-if="!loading" class="col-12">
|
||||||
|
<apexchart width="100%" height="120" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="service-chart-heatmap mt-5 mb-4">
|
<div class="service-chart-heatmap mt-5 mb-4">
|
||||||
<ServiceHeatmap :service="service"/>
|
<ServiceHeatmap :service="service"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="load_timedata" class="col-12">
|
|
||||||
|
|
||||||
<apexchart width="100%" height="420" type="rangeBar" :options="timeRangeOptions" :series="rangeSeries"></apexchart>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav v-if="service.failures" class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs">
|
|
||||||
<a @click="tab='failures'" class="flex-sm-fill text-sm-center nav-link active">Failures</a>
|
|
||||||
<a @click="tab='incidents'" class="flex-sm-fill text-sm-center nav-link">Incidents</a>
|
|
||||||
<a @click="tab='checkins'" v-if="$store.getters.token" class="flex-sm-fill text-sm-center nav-link">Checkins</a>
|
|
||||||
<a @click="tab='response'" v-if="$store.getters.token" class="flex-sm-fill text-sm-center nav-link">Response</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
|
||||||
<div v-if="service.failures" class="tab-content">
|
|
||||||
<div class="tab-pane fade active show">
|
|
||||||
<ServiceFailures :service="service"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade" :class="{active: tab === 'incidents'}" id="incidents">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade" :class="{show: tab === 'checkins'}" id="checkins">
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<Checkin :service="service"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade" :class="{show: tab === 'response'}" id="response">
|
|
||||||
<div class="col-12 mt-4">
|
|
||||||
<h3>Last Response</h3>
|
|
||||||
<textarea rows="8" class="form-control" readonly>invalid route</textarea>
|
|
||||||
<div class="form-group row mt-2">
|
|
||||||
<label for="last_status_code" class="col-sm-3 col-form-label">HTTP Status Code</label>
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<input type="text" id="last_status_code" class="form-control" value="200" readonly>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -99,6 +72,7 @@
|
||||||
import store from '../store'
|
import store from '../store'
|
||||||
import flatPickr from 'vue-flatpickr-component';
|
import flatPickr from 'vue-flatpickr-component';
|
||||||
import 'flatpickr/dist/flatpickr.css';
|
import 'flatpickr/dist/flatpickr.css';
|
||||||
|
import AdvancedChart from "@/components/Service/AdvancedChart";
|
||||||
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
const axisOptions = {
|
const axisOptions = {
|
||||||
|
|
@ -128,6 +102,7 @@
|
||||||
export default {
|
export default {
|
||||||
name: 'Service',
|
name: 'Service',
|
||||||
components: {
|
components: {
|
||||||
|
AdvancedChart,
|
||||||
ServiceTopStats,
|
ServiceTopStats,
|
||||||
ServiceHeatmap,
|
ServiceHeatmap,
|
||||||
ServiceFailures,
|
ServiceFailures,
|
||||||
|
|
@ -137,16 +112,18 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
|
||||||
tab: "failures",
|
tab: "failures",
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
ready: true,
|
ready: true,
|
||||||
|
group: "1h",
|
||||||
data: null,
|
data: null,
|
||||||
|
uptime_data: null,
|
||||||
|
loading: true,
|
||||||
messages: [],
|
messages: [],
|
||||||
failures: [],
|
failures: [],
|
||||||
start_time: this.nowSubtract(84600 * 30),
|
start_time: this.nowSubtract(84600 * 30),
|
||||||
end_time: new Date(),
|
end_time: this.nowSubtract(0),
|
||||||
timedata: [],
|
timedata: null,
|
||||||
load_timedata: false,
|
load_timedata: false,
|
||||||
dailyRangeOpts: {
|
dailyRangeOpts: {
|
||||||
chart: {
|
chart: {
|
||||||
|
|
@ -157,8 +134,21 @@ export default {
|
||||||
},
|
},
|
||||||
timeRangeOptions: {
|
timeRangeOptions: {
|
||||||
chart: {
|
chart: {
|
||||||
height: 200,
|
id: 'uptime',
|
||||||
type: 'rangeBar'
|
height: 120,
|
||||||
|
type: 'rangeBar',
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
enabled: true
|
||||||
},
|
},
|
||||||
plotOptions: {
|
plotOptions: {
|
||||||
bar: {
|
bar: {
|
||||||
|
|
@ -170,16 +160,10 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: true,
|
enabled: false
|
||||||
formatter: (val, opts) => {
|
},
|
||||||
var label = opts.w.globals.labels[opts.dataPointIndex]
|
tooltip: {
|
||||||
var a = this.parseISO(val[0])
|
enabled: false,
|
||||||
var b = this.parseISO(val[1])
|
|
||||||
return label
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
colors: ['#f3f4f5', '#fff']
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime'
|
type: 'datetime'
|
||||||
|
|
@ -195,12 +179,32 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
chartOptions: {
|
chartOptions: {
|
||||||
|
noData: {
|
||||||
|
text: "Loading...",
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: -20,
|
||||||
|
style: {
|
||||||
|
color: "#bababa",
|
||||||
|
fontSize: '27px'
|
||||||
|
}
|
||||||
|
},
|
||||||
chart: {
|
chart: {
|
||||||
|
id: 'mainchart',
|
||||||
events: {
|
events: {
|
||||||
beforeZoom: async (chartContext, { xaxis }) => {
|
dataPointSelection: (event, chartContext, config) => {
|
||||||
|
window.console.log('slect')
|
||||||
|
window.console.log(event)
|
||||||
|
},
|
||||||
|
updated: (chartContext, config) => {
|
||||||
|
window.console.log('updated')
|
||||||
|
},
|
||||||
|
beforeZoom: (chartContext, { xaxis }) => {
|
||||||
const start = (xaxis.min / 1000).toFixed(0)
|
const start = (xaxis.min / 1000).toFixed(0)
|
||||||
const end = (xaxis.max / 1000).toFixed(0)
|
const end = (xaxis.max / 1000).toFixed(0)
|
||||||
await this.chartHits(start, end, "10m")
|
this.start_time = this.fromUnix(start)
|
||||||
|
this.end_time = this.fromUnix(end)
|
||||||
return {
|
return {
|
||||||
xaxis: {
|
xaxis: {
|
||||||
min: this.fromUnix(start),
|
min: this.fromUnix(start),
|
||||||
|
|
@ -208,6 +212,9 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
scrolled: (chartContext, { xaxis }) => {
|
||||||
|
window.console.log(xaxis)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
height: 500,
|
height: 500,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|
@ -233,20 +240,28 @@ export default {
|
||||||
lineCap: 'butt',
|
lineCap: 'butt',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
labels: {
|
labels: {
|
||||||
show: true
|
show: true
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
yaxis: {
|
tooltip: {
|
||||||
labels: {
|
enabled: false
|
||||||
show: true
|
}
|
||||||
},
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
show: true
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
size: 0,
|
||||||
|
strokeWidth: 0,
|
||||||
|
hover: {
|
||||||
|
size: undefined,
|
||||||
|
sizeOffset: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
theme: false,
|
theme: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -259,7 +274,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
val = (val * 0.01).toFixed(0) + " microseconds"
|
val = (val * 0.01).toFixed(0) + " microseconds"
|
||||||
}
|
}
|
||||||
return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
|
return `<div class="chartmarker"><span>Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
|
||||||
},
|
},
|
||||||
fixed: {
|
fixed: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -268,9 +283,8 @@ export default {
|
||||||
offsetY: 40,
|
offsetY: 40,
|
||||||
},
|
},
|
||||||
x: {
|
x: {
|
||||||
show: false,
|
show: true,
|
||||||
format: 'dd MMM',
|
|
||||||
formatter: undefined,
|
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
formatter: undefined,
|
formatter: undefined,
|
||||||
|
|
@ -320,63 +334,84 @@ export default {
|
||||||
core () {
|
core () {
|
||||||
return this.$store.getters.core
|
return this.$store.getters.core
|
||||||
},
|
},
|
||||||
uptime_data() {
|
params () {
|
||||||
const data = this.timedata.series.filter(g => g.online)
|
return {start: this.toUnix(new Date(this.start_time)), end: this.toUnix(new Date(this.end_time))}
|
||||||
const offData = this.timedata.series.filter(g => !g.online)
|
|
||||||
let arr = [];
|
|
||||||
data.forEach((d) => {
|
|
||||||
arr.push({
|
|
||||||
name: "Online", data: {
|
|
||||||
x: 'Online',
|
|
||||||
y: [
|
|
||||||
new Date(d.start).getTime(),
|
|
||||||
new Date(d.end).getTime()
|
|
||||||
],
|
|
||||||
fillColor: '#0db407'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
offData.forEach((d) => {
|
|
||||||
arr.push({
|
|
||||||
name: "offline", data: {
|
|
||||||
x: 'Offline',
|
|
||||||
y: [
|
|
||||||
new Date(d.start).getTime(),
|
|
||||||
new Date(d.end).getTime()
|
|
||||||
],
|
|
||||||
fillColor: '#b40707'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return arr
|
|
||||||
},
|
},
|
||||||
rangeSeries() {
|
id () {
|
||||||
return [{data: this.time_chart_data}]
|
return this.$route.params.id;
|
||||||
},
|
},
|
||||||
|
uptimeSeries () {
|
||||||
|
return this.timedata.series
|
||||||
|
},
|
||||||
|
mainChart () {
|
||||||
|
return [{
|
||||||
|
name: this.service.name,
|
||||||
|
...this.convertToChartData(this.data)
|
||||||
|
}]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
service: function(n, o) {
|
service: function(n, o) {
|
||||||
this.chartHits()
|
this.onnn()
|
||||||
this.fetchUptime()
|
|
||||||
},
|
},
|
||||||
load_timedata: function(n, o) {
|
load_timedata: function(n, o) {
|
||||||
this.chartHits()
|
this.onnn()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async mounted() {
|
||||||
this.id = this.$route.params.id;
|
if (!this.$store.getters.service) {
|
||||||
},
|
const s = await Api.service(this.id)
|
||||||
|
this.$store.commit('setService', s)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async updated_chart(start, end) {
|
||||||
|
this.start_time = start
|
||||||
|
this.end_time = end
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
async onnn() {
|
||||||
|
this.loading = true
|
||||||
|
await this.chartHits()
|
||||||
|
await this.fetchUptime()
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
async fetchUptime() {
|
async fetchUptime() {
|
||||||
this.timedata = await Api.service_uptime(this.id)
|
const uptime = await Api.service_uptime(this.id, this.params.start, this.params.end)
|
||||||
this.load_timedata = true
|
window.console.log(uptime)
|
||||||
},
|
this.uptime_data = this.parse_uptime(uptime)
|
||||||
async get() {
|
},
|
||||||
const s = this.$store.getters.serviceByAll(this.id)
|
parse_uptime(timedata) {
|
||||||
window.console.log("service: ", s)
|
const data = timedata.series.filter((g) => g.online) || []
|
||||||
this.getService(this.service)
|
const offData = timedata.series.filter((g) => !g.online) || []
|
||||||
this.messages = this.$store.getters.serviceMessages(this.service.id)
|
let arr = [];
|
||||||
},
|
window.console.log(data)
|
||||||
|
if (data) {
|
||||||
|
data.forEach((d) => {
|
||||||
|
arr.push({
|
||||||
|
x: 'Online',
|
||||||
|
y: [
|
||||||
|
new Date(d.start).getTime(),
|
||||||
|
new Date(d.end).getTime()
|
||||||
|
],
|
||||||
|
fillColor: '#0db407'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (offData) {
|
||||||
|
offData.forEach((d) => {
|
||||||
|
arr.push({
|
||||||
|
x: 'Offline',
|
||||||
|
y: [
|
||||||
|
new Date(d.start).getTime(),
|
||||||
|
new Date(d.end).getTime()
|
||||||
|
],
|
||||||
|
fillColor: '#b40707'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return [{data: arr}]
|
||||||
|
},
|
||||||
messageInRange(message) {
|
messageInRange(message) {
|
||||||
const start = this.isBetween(new Date(), message.start_on)
|
const start = this.isBetween(new Date(), message.start_on)
|
||||||
const end = this.isBetween(message.end_on, new Date())
|
const end = this.isBetween(message.end_on, new Date())
|
||||||
|
|
@ -387,31 +422,15 @@ export default {
|
||||||
await this.serviceFailures()
|
await this.serviceFailures()
|
||||||
},
|
},
|
||||||
async serviceFailures() {
|
async serviceFailures() {
|
||||||
let tt = this.startEndTimes()
|
this.failures = await Api.service_failures(this.service.id, this.params.start, this.params.end)
|
||||||
this.failures = await Api.service_failures(this.service.id, tt.start, tt.end)
|
|
||||||
},
|
},
|
||||||
async chartHits(start=0, end=99999999999, group="30m") {
|
async chartHits(start=0, end=99999999999) {
|
||||||
let tt = {};
|
this.data = await Api.service_hits(this.service.id, this.params.start, this.params.end, this.group, false)
|
||||||
if (start === 0) {
|
if (this.data.length === 0 && this.group !== "1h") {
|
||||||
tt = this.startEndTimes()
|
this.group = "1h"
|
||||||
} else {
|
|
||||||
tt = {start, end}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = await Api.service_hits(this.service.id, tt.start, tt.end, group, false)
|
|
||||||
if (this.data.length === 0 && group !== "1h") {
|
|
||||||
await this.chartHits("1h")
|
await this.chartHits("1h")
|
||||||
}
|
}
|
||||||
this.series = [{
|
|
||||||
name: this.service.name,
|
|
||||||
...this.convertToChartData(this.data)
|
|
||||||
}]
|
|
||||||
this.ready = true
|
this.ready = true
|
||||||
},
|
|
||||||
startEndTimes() {
|
|
||||||
const start = this.toUnix(this.service.stats.first_hit)
|
|
||||||
const end = this.toUnix(new Date())
|
|
||||||
return {start, end}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ import VueRouter from "vue-router";
|
||||||
import Setup from "./forms/Setup";
|
import Setup from "./forms/Setup";
|
||||||
|
|
||||||
import Api from "./API";
|
import Api from "./API";
|
||||||
import Incidents from "@/pages/Incidents";
|
import Incidents from "@/components/Dashboard/Incidents";
|
||||||
import Checkins from "@/pages/Checkins";
|
import Checkins from "@/components/Dashboard/Checkins";
|
||||||
import Failures from "@/pages/Failures";
|
import Failures from "@/components/Dashboard/Failures";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default new Vuex.Store({
|
||||||
core: {},
|
core: {},
|
||||||
token: null,
|
token: null,
|
||||||
services: [],
|
services: [],
|
||||||
|
service: null,
|
||||||
groups: [],
|
groups: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
users: [],
|
users: [],
|
||||||
|
|
@ -36,6 +37,7 @@ export default new Vuex.Store({
|
||||||
core: state => state.core,
|
core: state => state.core,
|
||||||
token: state => state.token,
|
token: state => state.token,
|
||||||
services: state => state.services,
|
services: state => state.services,
|
||||||
|
service: state => state.service,
|
||||||
groups: state => state.groups,
|
groups: state => state.groups,
|
||||||
messages: state => state.messages,
|
messages: state => state.messages,
|
||||||
incidents: state => state.incidents,
|
incidents: state => state.incidents,
|
||||||
|
|
@ -104,6 +106,9 @@ export default new Vuex.Store({
|
||||||
setToken (state, token) {
|
setToken (state, token) {
|
||||||
state.token = token
|
state.token = token
|
||||||
},
|
},
|
||||||
|
setService (state, service) {
|
||||||
|
state.service = service
|
||||||
|
},
|
||||||
setServices (state, services) {
|
setServices (state, services) {
|
||||||
state.services = services
|
state.services = services
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,6 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if c.Domain != app.Domain {
|
if c.Domain != app.Domain {
|
||||||
app.Domain = c.Domain
|
app.Domain = c.Domain
|
||||||
}
|
}
|
||||||
if c.Timezone != app.Timezone {
|
|
||||||
app.Timezone = c.Timezone
|
|
||||||
}
|
|
||||||
app.OAuth = c.OAuth
|
app.OAuth = c.OAuth
|
||||||
app.UseCdn = null.NewNullBool(c.UseCdn.Bool)
|
app.UseCdn = null.NewNullBool(c.UseCdn.Bool)
|
||||||
app.AllowReports = null.NewNullBool(c.AllowReports.Bool)
|
app.AllowReports = null.NewNullBool(c.AllowReports.Bool)
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,6 @@ func (s Storage) List() map[string]Item {
|
||||||
|
|
||||||
//Get a cached content by key
|
//Get a cached content by key
|
||||||
func (s Storage) Get(key string) []byte {
|
func (s Storage) Get(key string) []byte {
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
item := s.items[key]
|
item := s.items[key]
|
||||||
if item.Expired() {
|
if item.Expired() {
|
||||||
CacheStorage.Delete(key)
|
CacheStorage.Delete(key)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ import (
|
||||||
"github.com/statping/statping/types/services"
|
"github.com/statping/statping/types/services"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type serviceOrder struct {
|
type serviceOrder struct {
|
||||||
|
|
@ -185,131 +183,37 @@ func apiServiceTimeDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allFailures := service.AllFailures()
|
groupHits, err := database.ParseQueries(r, service.AllHits())
|
||||||
allHits := service.AllHits()
|
if err != nil {
|
||||||
|
sendErrorJson(err, w, r)
|
||||||
tMap := make(map[time.Time]bool)
|
return
|
||||||
for _, v := range allHits.List() {
|
|
||||||
tMap[v.CreatedAt] = true
|
|
||||||
}
|
|
||||||
for _, v := range allFailures.List() {
|
|
||||||
tMap[v.CreatedAt] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var servs []ser
|
groupFailures, err := database.ParseQueries(r, service.AllFailures())
|
||||||
for t, v := range tMap {
|
if err != nil {
|
||||||
s := ser{
|
sendErrorJson(err, w, r)
|
||||||
Time: t,
|
return
|
||||||
Online: v,
|
|
||||||
}
|
|
||||||
servs = append(servs, s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(ByTime(servs))
|
var allFailures []*failures.Failure
|
||||||
|
var allHits []*hits.Hit
|
||||||
|
|
||||||
var allTimes []series
|
if err := groupHits.Find(&allHits); err != nil {
|
||||||
online := servs[0].Online
|
sendErrorJson(err, w, r)
|
||||||
thisTime := servs[0].Time
|
return
|
||||||
for i := 0; i < len(servs); i++ {
|
|
||||||
v := servs[i]
|
|
||||||
|
|
||||||
if v.Online != online {
|
|
||||||
s := series{
|
|
||||||
Start: thisTime,
|
|
||||||
End: v.Time,
|
|
||||||
Duration: v.Time.Sub(thisTime).Milliseconds(),
|
|
||||||
Online: online,
|
|
||||||
}
|
|
||||||
allTimes = append(allTimes, s)
|
|
||||||
thisTime = v.Time
|
|
||||||
online = v.Online
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
first := servs[0].Time
|
if err := groupFailures.Find(&allFailures); err != nil {
|
||||||
last := servs[len(servs)-1].Time
|
sendErrorJson(err, w, r)
|
||||||
|
return
|
||||||
if !service.Online {
|
|
||||||
s := series{
|
|
||||||
Start: allTimes[len(allTimes)-1].End,
|
|
||||||
End: utils.Now(),
|
|
||||||
Duration: utils.Now().Sub(last).Milliseconds(),
|
|
||||||
Online: service.Online,
|
|
||||||
}
|
|
||||||
allTimes = append(allTimes, s)
|
|
||||||
} else {
|
|
||||||
l := allTimes[len(allTimes)-1]
|
|
||||||
allTimes[len(allTimes)-1] = series{
|
|
||||||
Start: l.Start,
|
|
||||||
End: utils.Now(),
|
|
||||||
Duration: utils.Now().Sub(l.Start).Milliseconds(),
|
|
||||||
Online: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jj := uptimeSeries{
|
uptimeData, err := service.UptimeData(allHits, allFailures)
|
||||||
Start: first,
|
if err != nil {
|
||||||
End: last,
|
sendErrorJson(err, w, r)
|
||||||
Uptime: addDurations(allTimes, true),
|
return
|
||||||
Downtime: addDurations(allTimes, false),
|
|
||||||
Series: allTimes,
|
|
||||||
}
|
}
|
||||||
|
returnJson(uptimeData, w, r)
|
||||||
returnJson(jj, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ByTime []ser
|
|
||||||
|
|
||||||
func (a ByTime) Len() int { return len(a) }
|
|
||||||
func (a ByTime) Less(i, j int) bool { return a[i].Time.Before(a[j].Time) }
|
|
||||||
func (a ByTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
func addDurations(s []series, on bool) int64 {
|
|
||||||
var dur int64
|
|
||||||
for _, v := range s {
|
|
||||||
if v.Online == on {
|
|
||||||
dur += v.Duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dur
|
|
||||||
}
|
|
||||||
|
|
||||||
type ser struct {
|
|
||||||
Time time.Time
|
|
||||||
Online bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func findNextFailure(m map[time.Time]bool, after time.Time, online bool) time.Time {
|
|
||||||
for k, v := range m {
|
|
||||||
if k.After(after) && v == online {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
//func calculateDuration(m map[time.Time]bool, on bool) time.Duration {
|
|
||||||
// var t time.Duration
|
|
||||||
// for t, v := range m {
|
|
||||||
// if v == on {
|
|
||||||
// t.
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
type uptimeSeries struct {
|
|
||||||
Start time.Time `json:"start"`
|
|
||||||
End time.Time `json:"end"`
|
|
||||||
Uptime int64 `json:"uptime"`
|
|
||||||
Downtime int64 `json:"downtime"`
|
|
||||||
Series []series `json:"series"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type series struct {
|
|
||||||
Start time.Time `json:"start"`
|
|
||||||
End time.Time `json:"end"`
|
|
||||||
Duration int64 `json:"duration"`
|
|
||||||
Online bool `json:"online"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
58
install.sh
58
install.sh
|
|
@ -32,7 +32,6 @@ statping_get_tarball() {
|
||||||
else
|
else
|
||||||
tar xzf $tarball_tmp -C "$temp"
|
tar xzf $tarball_tmp -C "$temp"
|
||||||
fi
|
fi
|
||||||
statping_verify_integrity "$temp"/statping
|
|
||||||
printf "$green> Installing to $DEST/statping\n"
|
printf "$green> Installing to $DEST/statping\n"
|
||||||
mv "$temp"/statping "$DEST"
|
mv "$temp"/statping "$DEST"
|
||||||
newversion=`$DEST/statping version`
|
newversion=`$DEST/statping version`
|
||||||
|
|
@ -50,47 +49,15 @@ statping_get_tarball() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verifies the GPG signature of the tarball
|
|
||||||
statping_verify_integrity() {
|
|
||||||
# Check if GPG is installed
|
|
||||||
if [[ -z "$(command -v gpg)" ]]; then
|
|
||||||
printf "$yellow> WARNING: GPG is not installed, integrity can not be verified!$reset\n"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$statping_GPG" == "no" ]; then
|
|
||||||
printf "$cyan> WARNING: Skipping GPG integrity check!$reset\n"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "$cyan> Verifying integrity with gpg key from $gpgurl...$reset\n"
|
|
||||||
# Grab the public key if it doesn't already exist
|
|
||||||
gpg --list-keys $gpg_key >/dev/null 2>&1 || (curl -sS -L $gpgurl | gpg --import)
|
|
||||||
|
|
||||||
if [ ! -f "$1.asc" ]; then
|
|
||||||
printf "$red> Could not download GPG signature for this Statping release. This means the release can not be verified!$reset\n"
|
|
||||||
statping_verify_or_quit "> Do you really want to continue?"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Actually perform the verification
|
|
||||||
if gpg --verify "$1.asc" $1 &> /dev/null; then
|
|
||||||
printf "$green> GPG signature looks good$reset\n"
|
|
||||||
else
|
|
||||||
printf "$red> GPG signature for this Statping release is invalid! This is BAD and may mean the release has been tampered with. It is strongly recommended that you report this to the Statping developers.$reset\n"
|
|
||||||
statping_verify_or_quit "> Do you really want to continue?"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
statping_reset() {
|
statping_reset() {
|
||||||
unset -f statping_install statping_reset statping_get_tarball statping_verify_integrity statping_verify_or_quit statping_brew_install getOS getArch
|
unset -f statping_install statping_reset statping_get_tarball statping_verify_or_quit statping_brew_install getOS getArch
|
||||||
}
|
}
|
||||||
|
|
||||||
statping_brew_install() {
|
statping_brew_install() {
|
||||||
if [[ -z "$(command -v brew --version)" ]]; then
|
if [[ -z "$(command -v brew --version)" ]]; then
|
||||||
printf "${white}Using Brew to install!$reset\n"
|
printf "${white}Using Brew to install!$reset\n"
|
||||||
printf "${yellow}---> brew tap hunterlong/statping$reset\n"
|
printf "${yellow}---> brew tap statping/statping$reset\n"
|
||||||
brew tap hunterlong/statping
|
brew tap statping/statping
|
||||||
printf "${yellow}---> brew install statping$reset\n"
|
printf "${yellow}---> brew install statping$reset\n"
|
||||||
brew install statping
|
brew install statping
|
||||||
printf "${green}Brew installation is complete!$reset\n"
|
printf "${green}Brew installation is complete!$reset\n"
|
||||||
|
|
@ -104,7 +71,7 @@ statping_install() {
|
||||||
printf "${white}Installing Statping!$reset\n"
|
printf "${white}Installing Statping!$reset\n"
|
||||||
getOS
|
getOS
|
||||||
getArch
|
getArch
|
||||||
if [ "$OS" == "osx" ]; then
|
if [ "$OS" == "darwin" ]; then
|
||||||
statping_brew_install
|
statping_brew_install
|
||||||
else
|
else
|
||||||
statping_get_tarball $OS $ARCH
|
statping_get_tarball $OS $ARCH
|
||||||
|
|
@ -136,6 +103,11 @@ getOS() {
|
||||||
DEST=/usr/local/bin
|
DEST=/usr/local/bin
|
||||||
alias ls='ls -G'
|
alias ls='ls -G'
|
||||||
;;
|
;;
|
||||||
|
'OpenBSD')
|
||||||
|
OS='openbsd'
|
||||||
|
DEST=/usr/local/bin
|
||||||
|
alias ls='ls -G'
|
||||||
|
;;
|
||||||
'WindowsNT')
|
'WindowsNT')
|
||||||
OS='windows'
|
OS='windows'
|
||||||
DEST=/usr/local/bin
|
DEST=/usr/local/bin
|
||||||
|
|
@ -149,11 +121,11 @@ getOS() {
|
||||||
DEST=/usr/local/bin
|
DEST=/usr/local/bin
|
||||||
;;
|
;;
|
||||||
'Darwin')
|
'Darwin')
|
||||||
OS='osx'
|
OS='darwin'
|
||||||
DEST=/usr/local/bin
|
DEST=/usr/local/bin
|
||||||
;;
|
;;
|
||||||
'SunOS')
|
'SunOS')
|
||||||
OS='solaris'
|
OS='linux'
|
||||||
DEST=/usr/local/bin
|
DEST=/usr/local/bin
|
||||||
;;
|
;;
|
||||||
'AIX') ;;
|
'AIX') ;;
|
||||||
|
|
@ -165,9 +137,13 @@ getOS() {
|
||||||
getArch() {
|
getArch() {
|
||||||
MACHINE_TYPE=`uname -m`
|
MACHINE_TYPE=`uname -m`
|
||||||
if [ ${MACHINE_TYPE} == 'x86_64' ]; then
|
if [ ${MACHINE_TYPE} == 'x86_64' ]; then
|
||||||
ARCH="x64"
|
ARCH="amd64"
|
||||||
|
elif [ ${MACHINE_TYPE} == 'arm' ]; then
|
||||||
|
ARCH="arm"
|
||||||
|
elif [ ${MACHINE_TYPE} == 'arm64' ] || [ ${MACHINE_TYPE} == 'aarch64' ] || [ ${MACHINE_TYPE} == 'armv8b' ] || [ ${MACHINE_TYPE} == 'armv8l' ] || [ ${MACHINE_TYPE} == 'aarch64_be' ]; then
|
||||||
|
ARCH="arm64"
|
||||||
else
|
else
|
||||||
ARCH="x32"
|
ARCH="386"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package notifiers
|
package notifiers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/prometheus/common/log"
|
|
||||||
"github.com/statping/statping/types/failures"
|
"github.com/statping/statping/types/failures"
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"github.com/statping/statping/types/services"
|
"github.com/statping/statping/types/services"
|
||||||
|
|
@ -9,6 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = utils.Log.WithField("type", "notifier")
|
||||||
|
|
||||||
func InitNotifiers() {
|
func InitNotifiers() {
|
||||||
Add(
|
Add(
|
||||||
slacker,
|
slacker,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -23,7 +23,7 @@ var testCheckin = &Checkin{
|
||||||
|
|
||||||
var testCheckinHits = []*CheckinHit{{
|
var testCheckinHits = []*CheckinHit{{
|
||||||
Checkin: 1,
|
Checkin: 1,
|
||||||
From: "0.0.0.0.0",
|
From: "0.0.0.0",
|
||||||
CreatedAt: utils.Now().Add(-30 * time.Second),
|
CreatedAt: utils.Now().Add(-30 * time.Second),
|
||||||
}, {
|
}, {
|
||||||
Checkin: 2,
|
Checkin: 2,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,9 @@ func All() []*Checkin {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkin) Create() error {
|
func (c *Checkin) Create() error {
|
||||||
c.ApiKey = utils.RandomString(32)
|
if c.ApiKey == "" {
|
||||||
|
c.ApiKey = utils.RandomString(32)
|
||||||
|
}
|
||||||
q := db.Create(c)
|
q := db.Create(c)
|
||||||
|
|
||||||
c.Start()
|
c.Start()
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ package checkins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/prometheus/common/log"
|
|
||||||
"github.com/statping/statping/types/failures"
|
"github.com/statping/statping/types/failures"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = utils.Log.WithField("type", "checkin")
|
||||||
|
|
||||||
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
|
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
|
||||||
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
|
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
|
||||||
between := utils.Now().Sub(utils.Now()).Seconds()
|
between := utils.Now().Sub(utils.Now()).Seconds()
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import (
|
||||||
|
|
||||||
func Samples() error {
|
func Samples() error {
|
||||||
checkin1 := &Checkin{
|
checkin1 := &Checkin{
|
||||||
Name: "Example Checkin 1",
|
Name: "Demo Checkin 1",
|
||||||
ServiceId: 1,
|
ServiceId: 1,
|
||||||
Interval: 300,
|
Interval: 300,
|
||||||
GracePeriod: 300,
|
GracePeriod: 300,
|
||||||
ApiKey: utils.RandomString(7),
|
ApiKey: "demoCheckin123",
|
||||||
}
|
}
|
||||||
if err := checkin1.Create(); err != nil {
|
if err := checkin1.Create(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ type Core struct {
|
||||||
Setup bool `gorm:"-" json:"setup"`
|
Setup bool `gorm:"-" json:"setup"`
|
||||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||||
UseCdn null.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
UseCdn null.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
||||||
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
|
||||||
LoggedIn bool `gorm:"-" json:"logged_in"`
|
LoggedIn bool `gorm:"-" json:"logged_in"`
|
||||||
IsAdmin bool `gorm:"-" json:"admin"`
|
IsAdmin bool `gorm:"-" json:"admin"`
|
||||||
AllowReports null.NullBool `gorm:"column:allow_reports;default:false" json:"allow_reports"`
|
AllowReports null.NullBool `gorm:"column:allow_reports;default:false" json:"allow_reports"`
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,15 @@ package services
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/statping/statping/types"
|
"github.com/statping/statping/types"
|
||||||
|
"github.com/statping/statping/types/failures"
|
||||||
|
"github.com/statping/statping/types/hits"
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -19,6 +23,145 @@ func (s *Service) Duration() time.Duration {
|
||||||
return time.Duration(s.Interval) * time.Second
|
return time.Duration(s.Interval) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start will create a channel for the service checking go routine
|
||||||
|
func (s *Service) UptimeData(hits []*hits.Hit, fails []*failures.Failure) (*UptimeSeries, error) {
|
||||||
|
if len(hits) == 0 {
|
||||||
|
return nil, errors.New("service does not have any successful hits")
|
||||||
|
}
|
||||||
|
// if theres no failures, then its been online 100%,
|
||||||
|
// return a series from created time, to current.
|
||||||
|
if len(fails) == 0 {
|
||||||
|
fistHit := hits[0]
|
||||||
|
duration := utils.Now().Sub(fistHit.CreatedAt).Milliseconds()
|
||||||
|
set := []series{
|
||||||
|
{
|
||||||
|
Start: fistHit.CreatedAt,
|
||||||
|
End: utils.Now(),
|
||||||
|
Duration: duration,
|
||||||
|
Online: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := &UptimeSeries{
|
||||||
|
Start: fistHit.CreatedAt,
|
||||||
|
End: utils.Now(),
|
||||||
|
Uptime: duration,
|
||||||
|
Downtime: 0,
|
||||||
|
Series: set,
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tMap := make(map[time.Time]bool)
|
||||||
|
|
||||||
|
for _, v := range hits {
|
||||||
|
tMap[v.CreatedAt] = true
|
||||||
|
}
|
||||||
|
for _, v := range fails {
|
||||||
|
tMap[v.CreatedAt] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var servs []ser
|
||||||
|
for t, v := range tMap {
|
||||||
|
s := ser{
|
||||||
|
Time: t,
|
||||||
|
Online: v,
|
||||||
|
}
|
||||||
|
servs = append(servs, s)
|
||||||
|
}
|
||||||
|
if len(servs) == 0 {
|
||||||
|
return nil, errors.New("error generating uptime data structure")
|
||||||
|
}
|
||||||
|
sort.Sort(ByTime(servs))
|
||||||
|
|
||||||
|
var allTimes []series
|
||||||
|
online := servs[0].Online
|
||||||
|
thisTime := servs[0].Time
|
||||||
|
for i := 0; i < len(servs); i++ {
|
||||||
|
v := servs[i]
|
||||||
|
if v.Online != online {
|
||||||
|
s := series{
|
||||||
|
Start: thisTime,
|
||||||
|
End: v.Time,
|
||||||
|
Duration: v.Time.Sub(thisTime).Milliseconds(),
|
||||||
|
Online: online,
|
||||||
|
}
|
||||||
|
allTimes = append(allTimes, s)
|
||||||
|
thisTime = v.Time
|
||||||
|
online = v.Online
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(allTimes) == 0 {
|
||||||
|
return nil, errors.New("error generating uptime series structure")
|
||||||
|
}
|
||||||
|
|
||||||
|
first := servs[0].Time
|
||||||
|
last := servs[len(servs)-1].Time
|
||||||
|
if !s.Online {
|
||||||
|
s := series{
|
||||||
|
Start: allTimes[len(allTimes)-1].End,
|
||||||
|
End: utils.Now(),
|
||||||
|
Duration: utils.Now().Sub(last).Milliseconds(),
|
||||||
|
Online: s.Online,
|
||||||
|
}
|
||||||
|
allTimes = append(allTimes, s)
|
||||||
|
} else {
|
||||||
|
l := allTimes[len(allTimes)-1]
|
||||||
|
s := series{
|
||||||
|
Start: l.Start,
|
||||||
|
End: utils.Now(),
|
||||||
|
Duration: utils.Now().Sub(l.Start).Milliseconds(),
|
||||||
|
Online: true,
|
||||||
|
}
|
||||||
|
allTimes = append(allTimes, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &UptimeSeries{
|
||||||
|
Start: first,
|
||||||
|
End: last,
|
||||||
|
Uptime: addDurations(allTimes, true),
|
||||||
|
Downtime: addDurations(allTimes, false),
|
||||||
|
Series: allTimes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDurations(s []series, on bool) int64 {
|
||||||
|
var dur int64
|
||||||
|
for _, v := range s {
|
||||||
|
if v.Online == on {
|
||||||
|
dur += v.Duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dur
|
||||||
|
}
|
||||||
|
|
||||||
|
type ser struct {
|
||||||
|
Time time.Time
|
||||||
|
Online bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type UptimeSeries struct {
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
Uptime int64 `json:"uptime"`
|
||||||
|
Downtime int64 `json:"downtime"`
|
||||||
|
Series []series `json:"series"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ByTime []ser
|
||||||
|
|
||||||
|
func (a ByTime) Len() int { return len(a) }
|
||||||
|
func (a ByTime) Less(i, j int) bool { return a[i].Time.Before(a[j].Time) }
|
||||||
|
func (a ByTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
type series struct {
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
Duration int64 `json:"duration"`
|
||||||
|
Online bool `json:"online"`
|
||||||
|
}
|
||||||
|
|
||||||
// Start will create a channel for the service checking go routine
|
// Start will create a channel for the service checking go routine
|
||||||
func (s *Service) Start() {
|
func (s *Service) Start() {
|
||||||
if s.IsRunning() {
|
if s.IsRunning() {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/prometheus/common/log"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package users
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/prometheus/common/log"
|
|
||||||
"github.com/statping/statping/database"
|
"github.com/statping/statping/database"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var db database.Database
|
var (
|
||||||
|
db database.Database
|
||||||
|
log = utils.Log.WithField("type", "user")
|
||||||
|
)
|
||||||
|
|
||||||
func SetDB(database database.Database) {
|
func SetDB(database database.Database) {
|
||||||
db = database.Model(&User{})
|
db = database.Model(&User{})
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/prometheus/common/log"
|
|
||||||
Logger "github.com/sirupsen/logrus"
|
Logger "github.com/sirupsen/logrus"
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
@ -47,7 +46,7 @@ func SentryInit(v *string, allow bool) {
|
||||||
Environment: goEnv,
|
Environment: goEnv,
|
||||||
Release: version,
|
Release: version,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Errorln(err)
|
Log.Errorln(err)
|
||||||
}
|
}
|
||||||
Log.Infoln("Error Reporting initiated, thank you!")
|
Log.Infoln("Error Reporting initiated, thank you!")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.90.25
|
0.90.26
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue