mirror of https://github.com/statping/statping
Merge branch 'master' into feature/tls-renegotiation
commit
999037bb05
|
@ -1,2 +1,3 @@
|
|||
github: hunterlong
|
||||
custom: ['https://www.buymeacoffee.com/hunterlong']
|
||||
patreon: statping
|
||||
custom: ['https://www.nfoservers.com/donate.pl?force_recipient=1&recipient=info%40socialeck.com', 'https://www.buymeacoffee.com/hunterlong']
|
||||
|
|
|
@ -20,4 +20,4 @@ A clear and concise description of what you expected to happen.
|
|||
### Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
[](https://slack.statping.com/) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statping)
|
||||
[](https://slack.statping.com/) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statping)
|
||||
|
|
|
@ -19,4 +19,4 @@ I'm always frustrated when [...]
|
|||
### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
[](https://slack.statping.com/) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statping)
|
||||
[](https://slack.statping.com/) [](https://github.com/statping/statping/releases/latest) [](https://travis-ci.com/hunterlong/statping)
|
||||
|
|
|
@ -3,6 +3,7 @@ snap
|
|||
prime
|
||||
stage
|
||||
parts
|
||||
releases
|
||||
core/rice-box.go
|
||||
config.yml
|
||||
*.db
|
||||
|
|
|
@ -10,6 +10,7 @@ before_script:
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
||||
env:
|
||||
global:
|
||||
- "PATH=$HOME/.local/bin:$PATH"
|
||||
|
@ -17,6 +18,7 @@ env:
|
|||
- DB_USER=travis
|
||||
- DB_PASS=
|
||||
- DB_DATABASE=test
|
||||
- GO_ENV=test
|
||||
- STATPING_DIR=$GOPATH/src/github.com/statping/statping
|
||||
go: 1.14
|
||||
go_import_path: github.com/statping/statping
|
||||
|
@ -29,7 +31,6 @@ install:
|
|||
- "make test-deps yarn clean compile install"
|
||||
language: go
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
packages:
|
||||
- libgconf-2-4
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
# 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
|
||||
- Added string response on OnTest for Notifiers
|
||||
- Modified UI to show user the response for a Notifier.
|
||||
- Modified some Notifiers title's
|
||||
- Added more Cypress e2e testing
|
||||
- Modified Incidents form and UX.
|
||||
- Added /api/services/{id}/uptime_data API endpoint to show online/offline durations as a series for charts.
|
||||
- Modified index page to automatically refresh Service details on interval
|
||||
|
||||
# 0.90.24
|
||||
- Fixed login form from not showing
|
||||
|
|
129
Makefile
129
Makefile
|
@ -5,13 +5,15 @@ GOBUILD=go build -a
|
|||
GOVERSION=1.14.0
|
||||
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=
|
||||
TRVIS_SECRET=O3/2KTOV8krv+yZ1EB/7D1RQRe6NdpFUEJNJkMS/ollYqmz3x2mCO7yIgIJKCKguLXZxjM6CxJcjlCrvUwibL+8BBp7xJe4XFIOrjkPvbbVPry4HkFZCf2GfcUK6o4AByQ+RYqsW2F17Fp9KLQ1rL3OT3eLTwCAGKx3tlY8y+an43zkmo5dN64V6sawx26fh6XTfww590ey+ltgQTjf8UPNup2wZmGvMo9Hwvh/bYR/47bR6PlBh6vhlKWyotKf2Fz1Bevbu0zc35pee5YlsrHR+oSF+/nNd/dOij34BhtqQikUR+zQVy9yty8SlmneVwD3yOENvlF+8roeKIXb6P6eZnSMHvelhWpAFTwDXq2N3d/FIgrQtLxsAFTI3nTHvZgs6OoTd6dA0wkhuIGLxaL3FOeztCdxP5J/CQ9GUcTvifh5ArGGwYxRxQU6rTgtebJcNtXFISP9CEUR6rwRtb6ax7h6f1SbjUGAdxt+r2LbEVEk4ZlwHvdJ2DtzJHT5DQtLrqq/CTUgJ8SJFMkrJMp/pPznKhzN4qvd8oQJXygSXX/gz92MvoX0xgpNeLsUdAn+PL9KketfR+QYosBz04d8k05E+aTqGaU7FUCHPTLwlOFvLD8Gbv0zsC/PWgSLXTBlcqLEz5PHwPVHTcVzspKj/IyYimXpCSbvu1YOIjyc=
|
||||
PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }'
|
||||
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "merge_mode": "replace", "language": "go", "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": "$$TAG_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", "go": 1.14, "install": true, "sudo": "required", "services": ["docker"], "env": { "secure": "${TRVIS_SECRET}" }, "before_deploy": ["git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [{ "provider": "releases", "api_key": "$$GITHUB_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": { "branch": "master" } }], "before_script": ["rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install stable", "nvm install 10.17.0", "nvm use 10.17.0 --default", "npm install -g sass yarn cross-env", "pip install --user awscli"], "script": ["make release"], "after_success": [], "after_deploy": ["make post-release"] } } }'
|
||||
TEST_DIR=$(GOPATH)/src/github.com/statping/statping
|
||||
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
|
||||
OS = darwin freebsd linux openbsd
|
||||
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:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans
|
||||
|
@ -27,14 +29,14 @@ lite: clean
|
|||
|
||||
reup: down clean compose-build-full up
|
||||
|
||||
test: clean
|
||||
go test -v -p=4 -ldflags="-X main.VERSION=testing" -coverprofile=coverage.out ./...
|
||||
test: clean compile
|
||||
go test -v -p=1 -ldflags="-X main.VERSION=testing" -coverprofile=coverage.out ./...
|
||||
|
||||
# build all arch's and release Statping
|
||||
release: test-deps
|
||||
wget -O statping.gpg $(SIGN_URL)
|
||||
gpg --import statping.gpg
|
||||
make build-all upload_to_s3
|
||||
make build-all
|
||||
|
||||
test-ci: clean compile test-deps
|
||||
SASS=`which sass` go test -v -covermode=count -coverprofile=coverage.out -p=1 ./...
|
||||
|
@ -55,6 +57,9 @@ test-deps:
|
|||
go get github.com/mattn/goveralls
|
||||
go get github.com/GeertJohan/go.rice/rice
|
||||
|
||||
deps:
|
||||
go get -d -v -t ./...
|
||||
|
||||
protoc:
|
||||
cd types/proto && protoc --gofast_out=plugins=grpc:. statping.proto
|
||||
|
||||
|
@ -146,6 +151,34 @@ install-local: build
|
|||
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/; \
|
||||
GO111MODULE="on" GOOS=$$os GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-$$os-$$arch/statping ${PWD}/cmd; \
|
||||
chmod +x releases/statping-$$os-$$arch/statping; \
|
||||
tar -czf releases/statping-$$os-$$arch.tar.gz -C releases/statping-$$os-$$arch statping; \
|
||||
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/; \
|
||||
GO111MODULE="on" GOOS=windows GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-windows-$$arch/statping.exe ${PWD}/cmd; \
|
||||
chmod +x releases/statping-windows-$$arch/statping.exe; \
|
||||
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
|
||||
clean:
|
||||
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 "*.cpu" -type f -delete
|
||||
find . -name "*.mem" -type f -delete
|
||||
rm -rf {build,tmp}
|
||||
rm -rf {build,releases,tmp}
|
||||
|
||||
print_details:
|
||||
@echo \==== Statping Development Instance ====
|
||||
|
@ -186,76 +219,36 @@ print_details:
|
|||
@echo \==== Monitoring and IDE ====
|
||||
@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
|
||||
$(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS)
|
||||
|
||||
# build Statping using a travis ci trigger
|
||||
travis-build: travis_s3_creds
|
||||
travis-build:
|
||||
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/statping%2Fstatping/requests
|
||||
curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER)
|
||||
|
||||
download-key:
|
||||
wget -O statping.gpg $(SIGN_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 statping/statping:latest --no-cache -f Dockerfile .
|
||||
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
|
||||
publish-dev:
|
||||
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
|
||||
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
|
||||
|
@ -276,15 +269,25 @@ sign-all:
|
|||
valid-sign:
|
||||
gpg --verify statping.asc
|
||||
|
||||
# install xgo and pull the xgo docker image
|
||||
xgo-install: clean
|
||||
go get github.com/crazy-max/xgo
|
||||
docker pull crazymax/xgo:${GOVERSION}
|
||||
|
||||
sentry-release:
|
||||
sentry-cli releases new -p backend -p frontend v${VERSION}
|
||||
sentry-cli releases set-commits --auto v${VERSION}
|
||||
sentry-cli releases finalize v${VERSION}
|
||||
|
||||
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release
|
||||
snapcraft: clean compile build-bin
|
||||
PWD=$(shell pwd)
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}/build/statping-linux-amd64.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=amd64"
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}/build/statping-linux-386.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=i386"
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}/build/statping-linux-arm64.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=arm64"
|
||||
snapcraft clean statping -s pull
|
||||
docker run --rm -v ${PWD}/build/statping-linux-arm.tar.gz:/build/statping-linux.tar.gz -w /build --env VERSION=${VERSION} snapcore/snapcraft bash -c "apt update && snapcraft --target-arch=armhf"
|
||||
snapcraft push statping_${VERSION}_amd64.snap --release stable
|
||||
snapcraft push statping_${VERSION}_arm64.snap --release stable
|
||||
snapcraft push statping_${VERSION}_i386.snap --release stable
|
||||
snapcraft push statping_${VERSION}_armhf.snap --release stable
|
||||
|
||||
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-bin build-win build-all
|
||||
.SILENT: travis_s3_creds
|
||||
|
|
2
app.json
2
app.json
|
@ -2,6 +2,6 @@
|
|||
"name": "Statping",
|
||||
"description": "Statping Server Monitoring with Status Page",
|
||||
"repository": "https://github.com/statping/statping",
|
||||
"logo": "https://raw.githubusercontent.com/hunterlong/statping/master/source/tmpl/banner.png",
|
||||
"logo": "https://raw.githubusercontent.com/statping/statping/master/source/tmpl/banner.png",
|
||||
"keywords": ["statping", "server", "monitoring", "status page","golang", "go"]
|
||||
}
|
||||
|
|
|
@ -349,7 +349,7 @@ func HelpEcho() {
|
|||
}
|
||||
|
||||
func checkGithubUpdates() (githubResponse, error) {
|
||||
url := "https://api.github.com/repos/hunterlong/statping/releases/latest"
|
||||
url := "https://api.github.com/repos/statping/statping/releases/latest"
|
||||
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true)
|
||||
if err != nil {
|
||||
return githubResponse{}, err
|
||||
|
|
|
@ -146,6 +146,45 @@ type isObject interface {
|
|||
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) {
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
|
@ -169,6 +208,9 @@ func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
|||
log.Errorln(err)
|
||||
groupDur = 1 * time.Hour
|
||||
}
|
||||
if endField == 0 {
|
||||
endField = utils.Now().Unix()
|
||||
}
|
||||
|
||||
query := &GroupQuery{
|
||||
Start: time.Unix(startField, 0).UTC(),
|
||||
|
|
|
@ -7,7 +7,7 @@ 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
|
||||
WORKDIR /go/src/github.com/statping/statping
|
||||
|
||||
ADD go.mod go.sum version.txt ./
|
||||
|
||||
|
@ -22,7 +22,7 @@ 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
|
||||
ENV STATPING_DIR=/go/src/github.com/statping/statping
|
||||
|
||||
EXPOSE 8585
|
||||
EXPOSE 8888
|
||||
|
|
|
@ -21,7 +21,7 @@ services:
|
|||
# COMMIT: DEV
|
||||
# restart: on-failure
|
||||
# volumes:
|
||||
# - ./:/go/src/github.com/hunterlong/statping
|
||||
# - ./:/go/src/github.com/statping/statping
|
||||
# environment:
|
||||
# VIRTUAL_HOST: local.statping.com
|
||||
# VIRTUAL_PORT: 8888
|
||||
|
|
|
@ -4,19 +4,19 @@ services:
|
|||
|
||||
statping_dev:
|
||||
container_name: statping_dev
|
||||
image: hunterlong/statping:dev
|
||||
image: statping/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/
|
||||
- ./cmd:/go/src/github.com/statping/statping/cmd/
|
||||
- ./core:/go/src/github.com/statping/statping/core/
|
||||
- ./database:/go/src/github.com/statping/statping/database/
|
||||
- ./dev:/go/src/github.com/statping/statping/dev/
|
||||
- ./frontend:/go/src/github.com/statping/statping/frontend/
|
||||
- ./handlers:/go/src/github.com/statping/statping/handlers/
|
||||
- ./notifiers:/go/src/github.com/statping/statping/notifiers/
|
||||
- ./source:/go/src/github.com/statping/statping/source/
|
||||
- ./types:/go/src/github.com/statping/statping/types/
|
||||
- ./utils:/go/src/github.com/statping/statping/utils/
|
||||
environment:
|
||||
DB_CONN: sqlite
|
||||
API_KEY: exampleapikey
|
||||
|
|
|
@ -612,5 +612,5 @@
|
|||
"title": "Statping",
|
||||
"uid": "6BzRjddmz",
|
||||
"version": 25,
|
||||
"description": "Monitor your websites and applications using Statping service. View more information at https://github.com/hunterlong/statping"
|
||||
"description": "Monitor your websites and applications using Statping service. View more information at https://github.com/statping/statping"
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
cd /home/ubuntu
|
||||
source /home/ubuntu/.profile
|
||||
sudo rm -rf startup.sh > /dev/null
|
||||
sudo curl -o startup.sh -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/hunterlong/statping/master/dev/startup.sh > /dev/null
|
||||
sudo curl -o startup.sh -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/statping/statping/master/dev/startup.sh > /dev/null
|
||||
sudo chmod +x startup.sh > /dev/null
|
||||
sudo rm -f docker-compose.yml > /dev/null
|
||||
|
||||
|
@ -21,7 +21,7 @@ EC_IP=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)
|
|||
|
||||
if [ "$LETSENCRYPT_HOST" = "" ]
|
||||
then
|
||||
sudo curl -o docker-compose.yml -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/hunterlong/statping/master/dev/docker-compose-single.yml > /dev/null
|
||||
sudo curl -o docker-compose.yml -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/statping/statping/master/dev/docker-compose-single.yml > /dev/null
|
||||
else
|
||||
printf " \n\n\n\nDomain found for SSL certificate - $LETSENCRYPT_HOST\n"
|
||||
printf "================================================================================================================\n"
|
||||
|
@ -33,7 +33,7 @@ else
|
|||
printf " A $LETSENCRYPT_HOST => $EC_IP (or use A record if you are using an Elastic IP)\n"
|
||||
printf "================================================================================================================\n\n\n"
|
||||
|
||||
sudo curl -o docker-compose.yml -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/hunterlong/statping/dev/docker-compose-ssl.yml > /dev/null
|
||||
sudo curl -o docker-compose.yml -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/statping/statping/dev/docker-compose-ssl.yml > /dev/null
|
||||
fi
|
||||
|
||||
sudo service docker start > /dev/null
|
||||
|
@ -49,7 +49,7 @@ fi
|
|||
|
||||
sudo docker system prune -af > /dev/null
|
||||
|
||||
sudo curl https://raw.githubusercontent.com/hunterlong/statping/dev/init.sh > /home/ubuntu/init.sh
|
||||
sudo curl https://raw.githubusercontent.com/statping/statping/dev/init.sh > /home/ubuntu/init.sh
|
||||
sudo chmod +x /home/ubuntu/init.sh > /dev/null
|
||||
|
||||
printf "\n\n\n\n\n Statping Status Page on EC2\n"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
OS=osx
|
||||
ARCH=x64
|
||||
REPO=github.com/hunterlong/statping
|
||||
REPO=github.com/statping/statping
|
||||
VERSION=$(curl -s "https://$REPO/releases/latest" | grep -o 'tag/[v.0-9]*' | awk -F/ '{print $2}')
|
||||
if [ `getconf LONG_BIT` = "64" ]
|
||||
then
|
||||
|
@ -24,4 +24,4 @@ curl -L -sS $FILE -o statping.tar.gz && tar xzf statping.tar.gz && rm statping.t
|
|||
chmod +x statping
|
||||
echo "Installing Statping to directory: /usr/local/bin/"
|
||||
mv statping /usr/local/bin/
|
||||
echo "Statping $VERSION has been successfully installed! Try 'statping version' to check it!"
|
||||
echo "Statping $VERSION has been successfully installed! Try 'statping version' to check it!"
|
||||
|
|
|
@ -2164,7 +2164,7 @@
|
|||
"response": []
|
||||
}
|
||||
],
|
||||
"description": "Statping contains multiple notifiers that will send you a notification whenever a service become offline, or online. You can create your own 3rd party notifier by reading more on the [Notifiers Wiki](https://github.com/hunterlong/statping/wiki/Notifiers) on the Github repo.",
|
||||
"description": "Statping contains multiple notifiers that will send you a notification whenever a service become offline, or online. You can create your own 3rd party notifier by reading more on the [Notifiers Wiki](https://github.com/statping/statping/wiki/Notifiers) on the Github repo.",
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
|
@ -3471,4 +3471,4 @@
|
|||
}
|
||||
],
|
||||
"protocolProfileBehavior": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
https://demo.statping.com/
|
||||
https://github.com/hunterlong/statping
|
||||
https://github.com/statping/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
|
||||
https://hub.docker.com/r/statping/statping/
|
||||
https://godoc.org/github.com/statping/statping
|
||||
https://hub.docker.com/r/statping/statping/
|
||||
https://goreportcard.com/report/github.com/statping/statping
|
||||
http://slack.statping.com
|
||||
https://www.google.com/search?q=do+a+barrel+roll
|
||||
https://travis-ci.com/hunterlong/statping
|
||||
https://travis-ci.com/statping/statping
|
||||
https://vue.statping.com
|
||||
http://gorm.io
|
||||
https://www.youtube.com/watch?v=mLOdar3jshI
|
||||
|
|
4
doc.go
4
doc.go
|
@ -8,7 +8,7 @@
|
|||
// the zip file from the latest releases link.
|
||||
//
|
||||
// // MacOS using homebrew
|
||||
// brew tap hunterlong/statping
|
||||
// brew tap statping/statping
|
||||
// brew install statping
|
||||
//
|
||||
// // Linux installation
|
||||
|
@ -18,7 +18,7 @@
|
|||
// Docker
|
||||
//
|
||||
// Statping can be built in many way, the best way is to use Docker!
|
||||
// docker run -it -p 8080:8080 hunterlong/statping
|
||||
// docker run -it -p 8080:8080 statping/statping
|
||||
//
|
||||
// Enjoy Statping and tell me any issues you might be having on Github. https://github.com/hunterlong
|
||||
package statping
|
||||
|
|
|
@ -4,7 +4,7 @@ services:
|
|||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
image: statping/statping:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- statping_data:/app
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"DB_PASS": "password123",
|
||||
"GO_ENV": "production"
|
||||
},
|
||||
"baseUrl": "http://localhost:8080",
|
||||
"chromeWebSecurity": false,
|
||||
"defaultCommandTimeout": 30000,
|
||||
"requestTimeout": 30000,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import "../support/commands"
|
||||
|
||||
context('Annoucements Tests', () => {
|
||||
context('Announcements Tests', () => {
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
<base href="{{BasePath}}">
|
||||
{{if USE_CDN}}
|
||||
<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/css/base.css">
|
||||
<link rel="stylesheet" href="https://assets.statping.com/font/all.css">
|
||||
<link rel="stylesheet" href="https://assets.statping.com/vendor.css">
|
||||
<link rel="stylesheet" href="https://assets.statping.com/style.css">
|
||||
<link rel="stylesheet" href="https://assets.statping.com/main.css">
|
||||
{{else}}
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||
{{if USING_ASSETS}}
|
||||
|
@ -33,11 +33,11 @@
|
|||
<div id="app" class="statping_container"></div>
|
||||
|
||||
{{if USE_CDN}}
|
||||
<script src="https://assets.statping.com/js/bundle.js"></script>
|
||||
<script src="https://assets.statping.com/js/vendor.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/js/polyfill.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/js/style.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/js/main.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/bundle.js"></script>
|
||||
<script src="https://assets.statping.com/vendor.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/polyfill.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/style.chunk.js"></script>
|
||||
<script src="https://assets.statping.com/main.chunk.js"></script>
|
||||
{{else}}
|
||||
<% _.each(htmlWebpackPlugin.tags.bodyTags, function(bodyTag) { %>
|
||||
<%= bodyTag %> <% }) %>
|
||||
|
|
|
@ -56,6 +56,10 @@ class Api {
|
|||
return axios.get('api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_uptime(id, start, end) {
|
||||
return axios.get('api/services/' + id + '/uptime_data?start=' + start + '&end=' + end).then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_heatmap(id, start, end, group) {
|
||||
return axios.get('api/services/' + id + '/heatmap').then(response => (response.data))
|
||||
}
|
||||
|
@ -118,7 +122,7 @@ class Api {
|
|||
}
|
||||
|
||||
async incident_updates(incident) {
|
||||
return axios.post('api/incidents/'+incident.id+'/updates', data).then(response => (response.data))
|
||||
return axios.get('api/incidents/'+incident.id+'/updates').then(response => (response.data))
|
||||
}
|
||||
|
||||
async incident_update_create(update) {
|
||||
|
@ -129,12 +133,12 @@ class Api {
|
|||
return axios.delete('api/incidents/'+update.incident+'/updates/'+update.id).then(response => (response.data))
|
||||
}
|
||||
|
||||
async incidents_service(service) {
|
||||
return axios.get('api/services/'+service.id+'/incidents').then(response => (response.data))
|
||||
async incidents_service(id) {
|
||||
return axios.get('api/services/'+id+'/incidents').then(response => (response.data))
|
||||
}
|
||||
|
||||
async incident_create(service, data) {
|
||||
return axios.post('api/services/'+service.id+'/incidents', data).then(response => (response.data))
|
||||
async incident_create(service_id, data) {
|
||||
return axios.post('api/services/'+service_id+'/incidents', data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async incident_delete(incident) {
|
||||
|
|
|
@ -28,8 +28,8 @@ HTML,BODY {
|
|||
background-color: white;
|
||||
margin: 6px;
|
||||
height: 26px;
|
||||
font-size: 10pt;
|
||||
padding: 3px 7px;
|
||||
font-size: 8pt;
|
||||
padding: 5px 7px;
|
||||
border: 1px solid #a7a7a7;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
@ -344,10 +344,33 @@ HTML,BODY {
|
|||
background-color: #ffbbbb;
|
||||
}
|
||||
|
||||
.btn-white {
|
||||
background-color: white;
|
||||
border: 1px solid #d8d8d8;
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
.btn-white:hover {
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #bdbdbd;
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
.braker {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.10);
|
||||
width: 98%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.text-dim {
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: $service-background;
|
||||
border: $service-border;
|
||||
//box-shadow: 0px 2px 11px 1px rgba(0, 0, 0, 0.13);
|
||||
box-shadow: 0px 3px 6px 1px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div class="col-12">
|
||||
<h2>{{service.name}} Checkins</h2>
|
||||
<p class="mb-3">Tell your service to send a routine HTTP request to a Statping Checkin.</p>
|
||||
<div v-for="(checkin, i) in checkins" class="col-12 alert alert-light" role="alert">
|
||||
<span class="badge badge-pill badge-info text-uppercase">{{checkin.name}}</span>
|
||||
<span class="float-right font-2">Last checkin {{ago(checkin.last_hit)}}</span>
|
||||
<span class="float-right font-2 mr-3">Check Every {{checkin.interval}} seconds</span>
|
||||
<span class="float-right font-2 mr-3">Grace Period {{checkin.grace}} seconds</span>
|
||||
<span class="d-block mt-2">
|
||||
<input type="text" class="form-control" :value="`${core.domain}/checkin/${checkin.api_key}`" readonly>
|
||||
<span class="small">Send a GET request to this URL every {{checkin.interval}} seconds
|
||||
<button @click="deleteCheckin(checkin)" type="button" class="btn btn-danger btn-xs float-right mt-1">Delete</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-12 alert alert-light">
|
||||
<form @submit.prevent="saveCheckin">
|
||||
<div class="form-group row">
|
||||
<div class="col-5">
|
||||
<label for="checkin_interval" class="col-form-label">Checkin Name</label>
|
||||
<input v-model="checkin.name" type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<label for="checkin_interval" class="col-form-label">Interval</label>
|
||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<label for="grace_period" class="col-form-label">Grace Period</label>
|
||||
<input v-model="checkin.grace" type="number" name="grace" class="form-control" id="grace_period" placeholder="10">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="col-form-label"></label>
|
||||
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-primary d-block mt-2">Save Checkin</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
|
||||
export default {
|
||||
name: 'Checkins',
|
||||
data() {
|
||||
return {
|
||||
service: {},
|
||||
ready: false,
|
||||
checkin: {
|
||||
name: "",
|
||||
interval: 60,
|
||||
grace: 60,
|
||||
service_id: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checkins() {
|
||||
return this.$store.getters.serviceCheckins(this.service.id)
|
||||
},
|
||||
core() {
|
||||
return this.$store.getters.core
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
if (this.$route.params) {
|
||||
const id = this.$route.params.id
|
||||
this.service = await Api.service(id)
|
||||
this.checkin.service_id = this.service.id
|
||||
this.ready = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fixInts() {
|
||||
const c = this.checkin
|
||||
this.checkin.interval = parseInt(c.interval)
|
||||
this.checkin.grace = parseInt(c.grace)
|
||||
return this.checkin
|
||||
},
|
||||
async saveCheckin() {
|
||||
const c = this.fixInts()
|
||||
await Api.checkin_create(c)
|
||||
await this.updateCheckins()
|
||||
},
|
||||
async deleteCheckin(checkin) {
|
||||
await Api.checkin_delete(checkin)
|
||||
await this.updateCheckins()
|
||||
},
|
||||
async updateCheckins() {
|
||||
const checkins = await Api.checkins()
|
||||
this.$store.commit('setCheckins', checkins)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="col-12">
|
||||
<div class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Annoucements</div>
|
||||
<div class="card-body">
|
||||
<div class="card-header">Announcements</div>
|
||||
<div class="card-body pt-0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -3,16 +3,17 @@
|
|||
<div class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Services
|
||||
<router-link v-if="$store.state.admin" to="/dashboard/create_service" class="btn btn-sm btn-outline-success float-right">
|
||||
<font-awesome-icon icon="plus"/> Create
|
||||
</router-link></div>
|
||||
<div class="card-body">
|
||||
<font-awesome-icon icon="plus"/> Create
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
<ServicesList/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Groups</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body pt-0">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -35,10 +36,12 @@
|
|||
</td>
|
||||
<td class="text-right">
|
||||
<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>
|
||||
<a @click.prevent="deleteGroup(group)" href="#" class="btn btn-danger">
|
||||
<button @click.prevent="editGroup(group, edit)" href="#" class="btn btn-sm btn-outline-secondary">
|
||||
<font-awesome-icon icon="edit" />
|
||||
</button>
|
||||
<button @click.prevent="deleteGroup(group)" href="#" class="btn btn-sm btn-danger">
|
||||
<font-awesome-icon icon="times" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="col-12">
|
||||
<div class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Users</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body pt-0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div class="col-12">
|
||||
<h2>{{service.name}} Failures
|
||||
<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="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 class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">{{failure.issue}}</h5>
|
||||
<small>{{niceDate(failure.created_at)}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{failure.issue}}</p>
|
||||
</div>
|
||||
|
||||
<nav v-if="total > 4" class="mt-3">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item" :class="{'disabled': page===1}">
|
||||
<a @click.prevent="gotoPage(page-1)" :disabled="page===1" class="page-link" href="#" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
<li v-for="n in maxPages" class="page-item" :class="{'active': page === n}">
|
||||
<a @click.prevent="gotoPage(n)" class="page-link" href="#">{{n}}</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{'disabled': page===Math.floor(total / limit)}">
|
||||
<a @click.prevent="gotoPage(page+1)" :disabled="page===Math.floor(total / limit)" class="page-link" href="#" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="text-center">
|
||||
<span class="text-black-50">{{total}} Total</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
|
||||
export default {
|
||||
name: 'Failures',
|
||||
data() {
|
||||
return {
|
||||
service: {},
|
||||
failures: [],
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
total: 0,
|
||||
page: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
return Math.floor(this.total / this.limit)
|
||||
},
|
||||
maxPages() {
|
||||
const p = Math.floor(this.total / this.limit)
|
||||
if (p > 16) {
|
||||
return 16
|
||||
} else {
|
||||
return p
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.service = await Api.service(this.$route.params.id)
|
||||
this.total = this.service.stats.failures
|
||||
await this.gotoPage(1)
|
||||
},
|
||||
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) {
|
||||
this.page = page;
|
||||
this.offset = (page-1) * this.limit;
|
||||
await this.load()
|
||||
},
|
||||
async load() {
|
||||
this.failures = await Api.service_failures(this.service.id, 0, 9999999999, this.limit, this.offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div class="col-12">
|
||||
<div v-for="(incident, i) in incidents" class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Incident: {{incident.title}}
|
||||
<button @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
|
||||
<font-awesome-icon icon="times" /> Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<FormIncidentUpdates :incident="incident"/>
|
||||
|
||||
<span class="font-2 p-2 pl-3">Created: {{niceDate(incident.created_at)}} | Last Update: {{niceDate(incident.updated_at)}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card contain-card text-black-50 bg-white">
|
||||
<div class="card-header">Create Incident</div>
|
||||
<div class="card-body">
|
||||
<form @submit.prevent="createIncident">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="incident.title" type="text" name="title" class="form-control" id="title" placeholder="Incident Title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Description</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="incident.description" rows="5" name="description" class="form-control" id="description" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button @click.prevent="createIncident"
|
||||
:disabled="!incident.title || !incident.description"
|
||||
type="submit" class="btn btn-block btn-primary">
|
||||
Create Incident
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
import FormIncidentUpdates from "@/forms/IncidentUpdates";
|
||||
|
||||
export default {
|
||||
name: 'Incidents',
|
||||
components: {FormIncidentUpdates},
|
||||
data() {
|
||||
return {
|
||||
service_id: 0,
|
||||
ready: false,
|
||||
incidents: [],
|
||||
incident: {
|
||||
title: "",
|
||||
description: "",
|
||||
service: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
theID() {
|
||||
return this.$route.params.id
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadIncidents()
|
||||
},
|
||||
methods: {
|
||||
async getIncidents() {
|
||||
return await Api.incidents_service(this.theID)
|
||||
},
|
||||
async deleteIncident(incident) {
|
||||
let c = confirm(`Are you sure you want to delete '${incident.title}'?`)
|
||||
if (c) {
|
||||
await Api.incident_delete(incident)
|
||||
await this.loadIncidents()
|
||||
}
|
||||
},
|
||||
async loadIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service_id)
|
||||
},
|
||||
async createIncident() {
|
||||
await Api.incident_create(this.theID, this.incident)
|
||||
await this.loadIncidents()
|
||||
this.incident = {
|
||||
title: "",
|
||||
description: "",
|
||||
service: this.service_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
|
@ -9,7 +9,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<draggable id="services_list" tag="tbody" v-model="servicesList" handle=".drag_icon">
|
||||
<tr v-for="(service, index) in $store.getters.servicesInOrder" :key="service.id">
|
||||
<tr v-for="(service, index) in servicesList" :key="service.id">
|
||||
<td>
|
||||
<span v-if="$store.state.admin" class="drag_icon d-none d-md-inline">
|
||||
<font-awesome-icon icon="bars" class="mr-3"/>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<router-link to="/dashboard/users" class="nav-link">Users</router-link>
|
||||
</li>
|
||||
<li @click="navopen = !navopen" class="nav-item navbar-item">
|
||||
<router-link to="/dashboard/messages" class="nav-link">Annoucements</router-link>
|
||||
<router-link to="/dashboard/messages" class="nav-link">Announcements</router-link>
|
||||
</li>
|
||||
<li v-if="$store.state.admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
||||
<router-link to="/dashboard/settings" class="nav-link">Settings</router-link>
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
<div class="flex-fill service_day" v-for="(d, index) in failureData" :class="{'mini_error': d.amount > 0, 'mini_success': d.amount === 0}"></div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-6 text-left font-2 text-muted">30 Days Ago</div>
|
||||
<div class="col-6 text-right font-2 text-muted">Today</div>
|
||||
<div class="col-4 text-left font-2 text-muted">30 Days Ago</div>
|
||||
<div class="col-4 text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">
|
||||
{{service_txt}}
|
||||
</div>
|
||||
<div class="col-4 text-right font-2 text-muted">Today</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -28,6 +31,14 @@ export default {
|
|||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
service_txt() {
|
||||
if (!this.service.online) {
|
||||
return `Offline for ${this.ago(this.service.last_success)}`
|
||||
}
|
||||
return `${this.service.online_24_hours}% Uptime`
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.lastDaysFailures()
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div v-for="(incident, i) in incidents" class="col-12 mt-4">
|
||||
<h5>Incident: {{incident.title}}<span class="font-2 float-right">{{niceDate(incident.created_at)}}</span></h5>
|
||||
{{incident.description}}
|
||||
<div class="row">
|
||||
<div v-for="(update, i) in incident.updates.reverse()" v-bind:key="update.id" class="col-12 mt-3">
|
||||
<span class="col-2 badge text-uppercase" :class="badgeClass(update.type)">{{update.type}}</span>
|
||||
<span class="col-10">{{update.message}}</span>
|
||||
<span class="col-12 font-1 float-right text-black-50">{{ago(update.created_at)}} ago</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(incident, i) in incidents" class="col-12 mt-4 mb-3">
|
||||
<span class="braker mt-1 mb-3"></span>
|
||||
<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>
|
||||
|
||||
<UpdatesBlock :incident="incident"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
import UpdatesBlock from "@/components/Index/UpdatesBlock";
|
||||
|
||||
export default {
|
||||
name: 'IncidentsBlock',
|
||||
props: {
|
||||
components: {UpdatesBlock},
|
||||
props: {
|
||||
service: {
|
||||
type: Object
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -43,9 +45,13 @@ export default {
|
|||
return "badge-danger"
|
||||
}
|
||||
},
|
||||
async getIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
}
|
||||
async getIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service.id)
|
||||
},
|
||||
async incident_updates(incident) {
|
||||
await Api.incident_updates(incident).then((d) => {return d})
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div v-for="(update, i) in updates" v-bind:key="i" class="col-12 mt-3">
|
||||
<div class="col-md-2 col-12">
|
||||
<span class="badge text-uppercase" :class="badgeClass(update.type)">{{update.type}}</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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
|
||||
export default {
|
||||
name: 'UpdatesBlock',
|
||||
props: {
|
||||
incident: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
updates: null,
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.getIncidentUpdates()
|
||||
},
|
||||
methods: {
|
||||
badgeClass(val) {
|
||||
switch (val.toLowerCase()) {
|
||||
case "resolved":
|
||||
return "badge-success"
|
||||
case "update":
|
||||
return "badge-info"
|
||||
case "investigating":
|
||||
return "badge-danger"
|
||||
}
|
||||
},
|
||||
async getIncidentUpdates() {
|
||||
this.updates = await Api.incident_updates(this.incident)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -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 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="expanded = false" class="btn btn-sm float-right dyn-dark text-white" :class="{'btn-outline-success': service.online, 'bg-danger': !service.online}">Hide</button>
|
||||
</div>
|
||||
|
||||
<div v-if="expanded" class="row">
|
||||
<Analytics title="Last Failure" value="417 Days ago"/>
|
||||
<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}">
|
||||
View Service
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -63,6 +60,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
import Analytics from './Analytics';
|
||||
import ServiceChart from "./ServiceChart";
|
||||
import ServiceTopStats from "@/components/Service/ServiceTopStats";
|
||||
|
@ -72,13 +70,14 @@ export default {
|
|||
name: 'ServiceBlock',
|
||||
components: { Analytics, ServiceTopStats, ServiceChart},
|
||||
props: {
|
||||
service: {
|
||||
in_service: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer_func: null,
|
||||
expanded: false,
|
||||
visible: false,
|
||||
dropDownMenu: false,
|
||||
|
@ -115,10 +114,26 @@ export default {
|
|||
subtitle: "Last 7 Days",
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
track_service: null,
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer_func)
|
||||
},
|
||||
computed: {
|
||||
service() {
|
||||
return this.track_service
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.track_service = this.in_service
|
||||
},
|
||||
methods: {
|
||||
async setService() {
|
||||
await this.$store.commit('setService', this.service)
|
||||
this.$router.push('/service/'+this.service.id, {props: {in_service: this.service}})
|
||||
},
|
||||
async showMoreStats() {
|
||||
this.expanded = !this.expanded;
|
||||
|
||||
|
@ -142,6 +157,7 @@ export default {
|
|||
this.stats.low_ping.value = this.humanTime(pingData.low);
|
||||
},
|
||||
smallText(s) {
|
||||
const incidents = s.incidents
|
||||
if (s.online) {
|
||||
return `Online, last checked ${this.ago(s.last_success)}`
|
||||
} else {
|
||||
|
@ -155,6 +171,12 @@ export default {
|
|||
visibleChart(isVisible, entry) {
|
||||
if (isVisible && !this.visible) {
|
||||
this.visible = true
|
||||
|
||||
if (!this.timer_func) {
|
||||
this.timer_func = setInterval(async () => {
|
||||
this.track_service = await Api.service(this.service.id)
|
||||
}, this.track_service.check_interval * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,48 @@
|
|||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-3 p-md-1 pt-md-3 pb-md-1">
|
||||
<div class="card-body p-3 p-md-1 pt-md-1 pb-md-1">
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="loaded && service.online" class="col-12 pb-2">
|
||||
<div v-if="loaded" class="col-12 pb-2">
|
||||
|
||||
<div v-if="false" class="row mb-4 align-content-center">
|
||||
|
||||
<div v-if="!service.online" class="col-3 text-left">
|
||||
<span class="text-danger font-5 font-weight-bold">okko</span>
|
||||
<span class="font-2 d-block">Current Downtime</span>
|
||||
</div>
|
||||
|
||||
<div v-if="service.online" class="col-3 text-left">
|
||||
<span class="text-success font-5 font-weight-bold">
|
||||
{{service.online_24_hours.toString()}}%
|
||||
</span>
|
||||
<span class="font-2 d-block">Total Uptime</span>
|
||||
</div>
|
||||
|
||||
<div v-if="service.online" class="col-3 text-left">
|
||||
<span class="text-success font-5 font-weight-bold">
|
||||
0
|
||||
</span>
|
||||
<span class="font-2 d-block">Downtime Today</span>
|
||||
</div>
|
||||
|
||||
<div v-if="service.online" class="col-3 text-left">
|
||||
<span class="text-success font-5 font-weight-bold">
|
||||
{{(uptime.uptime / 10000).toFixed(0).toString()}}
|
||||
</span>
|
||||
<span class="font-2 d-block">Uptime Duration</span>
|
||||
</div>
|
||||
|
||||
<div class="col-3 text-left">
|
||||
<span class="text-danger font-5 font-weight-bold">
|
||||
{{service.failures_24_hours}}
|
||||
</span>
|
||||
<span class="font-2 d-block">Failures last 24 hours</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12 mt-2 mt-md-0 mb-3">
|
||||
<ServiceSparkLine :title="set2_name" subtitle="Latency Last 24 Hours" :series="set2"/>
|
||||
|
@ -22,33 +60,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="row mt-4 pt-1 mb-3 align-content-center">
|
||||
|
||||
<StatsGen :service="service"
|
||||
title="Since Yesterday"
|
||||
:start="this.toUnix(this.nowSubtract(86400 * 2))"
|
||||
:end="this.toUnix(this.nowSubtract(86400))"
|
||||
group="24h" expression="latencyPercent"/>
|
||||
|
||||
<StatsGen :service="service"
|
||||
title="7 Day Change"
|
||||
:start="this.toUnix(this.nowSubtract(86400 * 7))"
|
||||
:end="this.toUnix(this.now())"
|
||||
group="24h" expression="latencyPercent"/>
|
||||
|
||||
<StatsGen :service="service"
|
||||
title="Max Latency"
|
||||
:start="this.toUnix(this.nowSubtract(86400 * 2))"
|
||||
:end="this.toUnix(this.nowSubtract(86400))"
|
||||
group="24h" expression="latencyPercent"/>
|
||||
|
||||
<StatsGen :service="service"
|
||||
title="Uptime"
|
||||
:start="this.toUnix(this.nowSubtract(86400 * 2))"
|
||||
:end="this.toUnix(this.nowSubtract(86400))"
|
||||
group="24h" expression="latencyPercent"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
@ -56,32 +67,23 @@
|
|||
<div class="card-footer">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-3">
|
||||
<button @click.prevent="Tab('incident')" class="btn btn-block btn-outline-secondary incident" :class="{'text-white btn-secondary': openTab==='incident'}" >Incidents</button>
|
||||
<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">
|
||||
Incidents
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<button @click.prevent="Tab('checkin')" class="btn btn-block btn-outline-secondary checkin" :class="{'text-white btn-secondary': openTab==='checkin'}" >Checkins</button>
|
||||
<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">
|
||||
Checkins
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<button @click.prevent="Tab('failures')" class="btn btn-block btn-outline-secondary failures" :disabled="service.stats.failures === 0" :class="{'text-white btn-secondary': openTab==='failures'}">
|
||||
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span></button>
|
||||
<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">
|
||||
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="col-3 pt-2">
|
||||
<span class="text-black-50 float-right">{{service.online_7_days}}% Uptime</span>
|
||||
</div>
|
||||
|
||||
<div v-if="openTab === 'incident'" class="col-12 mt-4">
|
||||
<FormIncident :service="service" />
|
||||
</div>
|
||||
|
||||
<div v-if="openTab === 'checkin'" class="col-12 mt-4">
|
||||
<Checkin :service="service" />
|
||||
</div>
|
||||
|
||||
<div v-if="openTab === 'failures'" class="col-12 mt-4">
|
||||
<button @click.prevent="deleteFailures" class="btn btn-block btn-outline-secondary delete_failures" :disabled="service.stats.failures === 0">Delete Failures</button>
|
||||
|
||||
<ServiceFailures :service="service"/>
|
||||
<div class="col-12 col-md-3 mb-2 mb-md-0 mt-0 mt-md-1">
|
||||
<span class="text-black-50 float-md-right">{{service.online_7_days}}% Uptime</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -122,6 +124,7 @@
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
uptime: null,
|
||||
openTab: "",
|
||||
set1: [],
|
||||
set2: [],
|
||||
|
@ -139,22 +142,19 @@
|
|||
async setVisible(isVisible, entry) {
|
||||
if (isVisible && !this.visible) {
|
||||
await this.loadInfo()
|
||||
await this.getUptime()
|
||||
this.visible = true
|
||||
}
|
||||
},
|
||||
async getUptime() {
|
||||
this.uptime = await Api.service_uptime(this.service.id)
|
||||
},
|
||||
async loadInfo() {
|
||||
this.set1 = await this.getHits(24 * 7, "6h")
|
||||
this.set1_name = this.calc(this.set1)
|
||||
this.set2 = await this.getHits(24, "1h")
|
||||
this.set2_name = this.calc(this.set2)
|
||||
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) {
|
||||
if (this.openTab === name) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="col-3 text-center">
|
||||
<div class="col-3 text-left">
|
||||
<span class="text-success font-5 font-weight-bold">{{value}}</span>
|
||||
<span class="font-2 d-block">{{title}}</span>
|
||||
</div>
|
||||
|
@ -34,6 +34,9 @@
|
|||
expression: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
in_value: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -42,6 +45,9 @@
|
|||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (this.in_value) {
|
||||
this.value = this.in_value
|
||||
}
|
||||
await this.latencyYesterday();
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,61 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
<div v-for="(incident, i) in incidents" class="card contain-card text-black-50 bg-white mb-4">
|
||||
<div class="card-header">Incident: {{incident.title}}
|
||||
<button @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
|
||||
<font-awesome-icon icon="times" /> Delete
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body bg-light pt-3">
|
||||
<div v-for="(update, i) in incident.updates" class="alert alert-light" role="alert">
|
||||
<span class="badge badge-pill badge-info text-uppercase">{{update.type}}</span>
|
||||
<span class="float-right font-2">{{ago(update.created_at)}} ago</span>
|
||||
<span class="d-block mt-2">{{update.message}}
|
||||
<button @click="delete_update(update)" type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<FormIncidentUpdates :incident="incident"/>
|
||||
|
||||
<span class="font-2 mt-3">Created: {{niceDate(incident.created_at)}} | Last Update: {{niceDate(incident.updated_at)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card contain-card text-black-50 bg-white mb-5">
|
||||
<div class="card-header">Create Incident for {{service.name}}</div>
|
||||
<div class="card-body">
|
||||
<form @submit.prevent="createIncident">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="incident.title" type="text" name="title" class="form-control" id="title" placeholder="Incident Title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Description</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="incident.description" rows="5" name="description" class="form-control" id="description" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button @click.prevent="createIncident"
|
||||
:disabled="!incident.title || !incident.description"
|
||||
type="submit" class="btn btn-block btn-primary">
|
||||
Create Incident
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -89,29 +34,7 @@
|
|||
await this.loadIncidents()
|
||||
},
|
||||
methods: {
|
||||
async delete_update(update) {
|
||||
await Api.incident_update_delete(update)
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
},
|
||||
async loadIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service)
|
||||
},
|
||||
async createIncident() {
|
||||
await Api.incident_create(this.service, this.incident)
|
||||
await this.loadIncidents()
|
||||
this.incident = {
|
||||
title: "",
|
||||
description: "",
|
||||
service: this.service.id,
|
||||
}
|
||||
},
|
||||
async deleteIncident(incident) {
|
||||
let c = confirm(`Are you sure you want to delete '${incident.title}'?`)
|
||||
if (c) {
|
||||
await Api.incident_delete(incident)
|
||||
await this.loadIncidents()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
<template>
|
||||
<form class="row" @submit.prevent="createIncidentUpdate">
|
||||
<div class="card-body bg-light pt-3">
|
||||
|
||||
<div v-if="updates.length===0" class="alert alert-link text-danger">
|
||||
No updates found, create a new Incident Update below.
|
||||
</div>
|
||||
|
||||
<div v-for="(update, i) in updates">
|
||||
<div class="alert alert-light" role="alert">
|
||||
<span class="badge badge-pill badge-info text-uppercase">{{update.type}}</span>
|
||||
<span class="float-right font-2">{{ago(update.created_at)}} ago</span>
|
||||
<span class="d-block mt-2">{{update.message}}
|
||||
<button @click="delete_update(update)" type="button" class="close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<form class="row" @submit.prevent="createIncidentUpdate">
|
||||
<div class="col-3">
|
||||
<select v-model="incident_update.type" class="form-control">
|
||||
<option value="Investigating">Investigating</option>
|
||||
|
@ -21,6 +37,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -35,12 +52,13 @@
|
|||
},
|
||||
props: {
|
||||
incident: {
|
||||
type: Object
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
updates: [],
|
||||
updates: this.incident.updates,
|
||||
incident_update: {
|
||||
incident: this.incident.id,
|
||||
message: "",
|
||||
|
@ -48,16 +66,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
await this.loadUpdates()
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadUpdates()
|
||||
},
|
||||
methods: {
|
||||
async delete_update(update) {
|
||||
await Api.incident_update_delete(update)
|
||||
await this.loadUpdates()
|
||||
},
|
||||
methods: {
|
||||
async loadUpdates() {
|
||||
this.updates = await Api.incident_updates(this.incident)
|
||||
},
|
||||
async createIncidentUpdate() {
|
||||
await Api.incident_update_create(this.incident_update)
|
||||
await this.loadUpdates()
|
||||
},
|
||||
async loadUpdates() {
|
||||
this.updates = await Api.incident_updates(this.incident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="card contain-card text-black-50 bg-white mb-5">
|
||||
<div class="card-header">{{message.id ? `Update ${message.title}` : "Create Annoucement"}}
|
||||
<div class="card-header">{{message.id ? `Update ${message.title}` : "Create Announcement"}}
|
||||
|
||||
<transition name="slide-fade">
|
||||
<button @click="removeEdit" v-if="message.id" class="btn btn-sm float-right btn-danger btn-sm">Close</button>
|
||||
|
|
|
@ -156,10 +156,10 @@
|
|||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Enable Notifications</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span @click="service.allow_notifications = !!service.allow_notifications" class="switch float-left">
|
||||
<input v-model="service.allow_notifications" type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" v-bind:checked="service.allow_notifications">
|
||||
<label for="switch-notifications">Allow notifications to be sent for this service</label>
|
||||
</span>
|
||||
<span @click="service.allow_notifications = !!service.allow_notifications" class="switch float-left">
|
||||
<input v-model="service.allow_notifications" type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" v-bind:checked="service.allow_notifications">
|
||||
<label for="switch-notifications">Allow notifications to be sent for this service</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="service.allow_notifications" class="form-group row">
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="form-group">
|
||||
<label>Database Connection</label>
|
||||
<select @change="canSubmit" v-model="setup.db_connection" id="db_connection" class="form-control">
|
||||
<option value="sqlite">Sqlite</option>
|
||||
<option value="sqlite">SQLite</option>
|
||||
<option value="postgres">Postgres</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
</select>
|
||||
|
|
|
@ -42,6 +42,9 @@ export default Vue.mixin({
|
|||
dur(t1, t2) {
|
||||
return formatDistance(t1, t2)
|
||||
},
|
||||
format(val, type="EEEE, MMM do h:mma") {
|
||||
return format(val, type)
|
||||
},
|
||||
niceDate(val) {
|
||||
return format(parseISO(val), "EEEE, MMM do h:mma")
|
||||
},
|
||||
|
@ -119,6 +122,13 @@ export default Vue.mixin({
|
|||
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) {
|
||||
if (!data) {
|
||||
return {data: []}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<div class="col-12 full-col-12">
|
||||
<div v-for="(service, index) in services" :ref="service.id" v-bind:key="index">
|
||||
<ServiceBlock :service=service />
|
||||
<ServiceBlock :in_service=service />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="service" class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
|
||||
<div class="col-12 mb-4">
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
|||
</span>
|
||||
|
||||
<h4 class="mt-2">
|
||||
<router-link to="/" class="text-black-50 text-decoration-none">{{$store.getters.core.name}}</router-link> - <span class="text-muted">{{service.name}}</span>
|
||||
<router-link to="/" class="text-black-50 text-decoration-none">{{core.name}}</router-link> - <span class="text-muted">{{service.name}}</span>
|
||||
<span class="badge float-right d-none d-md-block" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||
{{service.online ? "ONLINE" : "OFFLINE"}}
|
||||
</span>
|
||||
|
@ -21,64 +21,42 @@
|
|||
</div>
|
||||
|
||||
<div class="row mt-5 mb-4">
|
||||
<span class="col-6 font-2">
|
||||
<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 />
|
||||
</span>
|
||||
<span class="col-6 font-2">
|
||||
<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 />
|
||||
</span>
|
||||
<div class="col-12 col-md-5 font-2 mb-3 mb-md-0">
|
||||
<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 />
|
||||
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
||||
</div>
|
||||
<div class="col-12 col-md-5 font-2 mb-3 mb-md-0">
|
||||
<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 v-if="series" class="service-chart-container">
|
||||
<apexchart width="100%" height="420" type="area" :options="chartOptions" :series="series"></apexchart>
|
||||
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
||||
|
||||
<div v-if="!loading" class="col-12">
|
||||
<apexchart width="100%" height="120" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
||||
</div>
|
||||
|
||||
<div class="service-chart-heatmap mt-5 mb-4">
|
||||
<ServiceHeatmap :service="service"/>
|
||||
</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>
|
||||
|
||||
|
@ -94,6 +72,7 @@
|
|||
import store from '../store'
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
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 axisOptions = {
|
||||
|
@ -123,6 +102,7 @@
|
|||
export default {
|
||||
name: 'Service',
|
||||
components: {
|
||||
AdvancedChart,
|
||||
ServiceTopStats,
|
||||
ServiceHeatmap,
|
||||
ServiceFailures,
|
||||
|
@ -132,15 +112,19 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
id: this.$route.params.id,
|
||||
tab: "failures",
|
||||
authenticated: false,
|
||||
ready: true,
|
||||
group: "1h",
|
||||
data: null,
|
||||
uptime_data: null,
|
||||
loading: true,
|
||||
messages: [],
|
||||
failures: [],
|
||||
start_time: this.nowSubtract(84600 * 30),
|
||||
end_time: new Date(),
|
||||
end_time: this.nowSubtract(0),
|
||||
timedata: null,
|
||||
load_timedata: false,
|
||||
dailyRangeOpts: {
|
||||
chart: {
|
||||
height: 500,
|
||||
|
@ -148,13 +132,79 @@ export default {
|
|||
type: "area",
|
||||
}
|
||||
},
|
||||
timeRangeOptions: {
|
||||
chart: {
|
||||
id: 'uptime',
|
||||
height: 120,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
enabled: true
|
||||
},
|
||||
zoom: {
|
||||
enabled: true
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true,
|
||||
distributed: true,
|
||||
dataLabels: {
|
||||
hideOverflowingLabels: false
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yaxis: {
|
||||
show: false
|
||||
},
|
||||
grid: {
|
||||
row: {
|
||||
colors: ['#f3f4f5', '#fff'],
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
chartOptions: {
|
||||
noData: {
|
||||
text: "Loading...",
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
offsetX: 0,
|
||||
offsetY: -20,
|
||||
style: {
|
||||
color: "#bababa",
|
||||
fontSize: '27px'
|
||||
}
|
||||
},
|
||||
chart: {
|
||||
id: 'mainchart',
|
||||
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 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 {
|
||||
xaxis: {
|
||||
min: this.fromUnix(start),
|
||||
|
@ -162,6 +212,9 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
scrolled: (chartContext, { xaxis }) => {
|
||||
window.console.log(xaxis)
|
||||
},
|
||||
},
|
||||
height: 500,
|
||||
width: "100%",
|
||||
|
@ -187,20 +240,28 @@ export default {
|
|||
lineCap: 'butt',
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
type: "datetime",
|
||||
labels: {
|
||||
show: true
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true
|
||||
}
|
||||
xaxis: {
|
||||
type: "datetime",
|
||||
labels: {
|
||||
show: true
|
||||
},
|
||||
yaxis: {
|
||||
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,
|
||||
|
@ -213,7 +274,7 @@ export default {
|
|||
} else {
|
||||
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: {
|
||||
enabled: true,
|
||||
|
@ -222,9 +283,8 @@ export default {
|
|||
offsetY: 40,
|
||||
},
|
||||
x: {
|
||||
show: false,
|
||||
format: 'dd MMM',
|
||||
formatter: undefined,
|
||||
show: true,
|
||||
|
||||
},
|
||||
y: {
|
||||
formatter: undefined,
|
||||
|
@ -268,64 +328,109 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
service () {
|
||||
return this.$store.getters.serviceByAll(this.id)
|
||||
}
|
||||
service () {
|
||||
return this.$store.getters.serviceByAll(this.id)
|
||||
},
|
||||
core () {
|
||||
return this.$store.getters.core
|
||||
},
|
||||
params () {
|
||||
return {start: this.toUnix(new Date(this.start_time)), end: this.toUnix(new Date(this.end_time))}
|
||||
},
|
||||
id () {
|
||||
return this.$route.params.id;
|
||||
},
|
||||
uptimeSeries () {
|
||||
return this.timedata.series
|
||||
},
|
||||
mainChart () {
|
||||
return [{
|
||||
name: this.service.name,
|
||||
...this.convertToChartData(this.data)
|
||||
}]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
service: function(n, o) {
|
||||
this.chartHits()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
service: function(n, o) {
|
||||
this.onnn()
|
||||
},
|
||||
load_timedata: function(n, o) {
|
||||
this.onnn()
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (!this.$store.getters.service) {
|
||||
const s = await Api.service(this.id)
|
||||
this.$store.commit('setService', s)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async get() {
|
||||
const s = store.getters.serviceByAll(this.id)
|
||||
window.console.log("service: ", s)
|
||||
this.getService(this.service)
|
||||
this.messages = this.$store.getters.serviceMessages(this.service.id)
|
||||
},
|
||||
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() {
|
||||
const uptime = await Api.service_uptime(this.id, this.params.start, this.params.end)
|
||||
window.console.log(uptime)
|
||||
this.uptime_data = this.parse_uptime(uptime)
|
||||
},
|
||||
parse_uptime(timedata) {
|
||||
const data = timedata.series.filter((g) => g.online) || []
|
||||
const offData = timedata.series.filter((g) => !g.online) || []
|
||||
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) {
|
||||
const start = this.isBetween(new Date(), message.start_on)
|
||||
const end = this.isBetween(message.end_on, new Date())
|
||||
return start && end
|
||||
},
|
||||
async getService(s) {
|
||||
async getService() {
|
||||
await this.chartHits()
|
||||
await this.serviceFailures()
|
||||
},
|
||||
async serviceFailures() {
|
||||
let tt = this.startEndTimes()
|
||||
|
||||
this.failures = await Api.service_failures(this.service.id, tt.start, tt.end)
|
||||
this.failures = await Api.service_failures(this.service.id, this.params.start, this.params.end)
|
||||
},
|
||||
async chartHits(start=0, end=99999999999, group="30m") {
|
||||
let tt = {};
|
||||
if (start === 0) {
|
||||
tt = this.startEndTimes()
|
||||
} 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") {
|
||||
async chartHits(start=0, end=99999999999) {
|
||||
this.data = await Api.service_hits(this.service.id, this.params.start, this.params.end, this.group, false)
|
||||
if (this.data.length === 0 && this.group !== "1h") {
|
||||
this.group = "1h"
|
||||
await this.chartHits("1h")
|
||||
}
|
||||
this.series = [{
|
||||
name: this.service.name,
|
||||
...this.convertToChartData(this.data)
|
||||
}]
|
||||
this.ready = true
|
||||
},
|
||||
startEndTimes() {
|
||||
const start = this.toUnix(this.service.stats.first_hit)
|
||||
const end = this.toUnix(new Date())
|
||||
return {start, end}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ import VueRouter from "vue-router";
|
|||
import Setup from "./forms/Setup";
|
||||
|
||||
import Api from "./API";
|
||||
import Incidents from "@/components/Dashboard/Incidents";
|
||||
import Checkins from "@/components/Dashboard/Checkins";
|
||||
import Failures from "@/components/Dashboard/Failures";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
@ -59,9 +62,27 @@ const routes = [
|
|||
},{
|
||||
path: 'edit_service/:id',
|
||||
component: EditService,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},{
|
||||
path: 'service/:id/incidents',
|
||||
component: Incidents,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},{
|
||||
path: 'service/:id/checkins',
|
||||
component: Checkins,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},{
|
||||
path: 'service/:id/failures',
|
||||
component: Failures,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},{
|
||||
path: 'messages',
|
||||
component: DashboardMessages,
|
||||
|
|
|
@ -22,6 +22,7 @@ export default new Vuex.Store({
|
|||
core: {},
|
||||
token: null,
|
||||
services: [],
|
||||
service: null,
|
||||
groups: [],
|
||||
messages: [],
|
||||
users: [],
|
||||
|
@ -36,6 +37,7 @@ export default new Vuex.Store({
|
|||
core: state => state.core,
|
||||
token: state => state.token,
|
||||
services: state => state.services,
|
||||
service: state => state.service,
|
||||
groups: state => state.groups,
|
||||
messages: state => state.messages,
|
||||
incidents: state => state.incidents,
|
||||
|
@ -104,6 +106,9 @@ export default new Vuex.Store({
|
|||
setToken (state, token) {
|
||||
state.token = token
|
||||
},
|
||||
setService (state, service) {
|
||||
state.service = service
|
||||
},
|
||||
setServices (state, services) {
|
||||
state.services = services
|
||||
},
|
||||
|
|
1
go.mod
1
go.mod
|
@ -1,5 +1,6 @@
|
|||
module github.com/statping/statping
|
||||
|
||||
// +heroku goVersion go1.14
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
|
|
|
@ -76,9 +76,6 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if c.Domain != app.Domain {
|
||||
app.Domain = c.Domain
|
||||
}
|
||||
if c.Timezone != app.Timezone {
|
||||
app.Timezone = c.Timezone
|
||||
}
|
||||
app.OAuth = c.OAuth
|
||||
app.UseCdn = null.NewNullBool(c.UseCdn.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
|
||||
func (s Storage) Get(key string) []byte {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
item := s.items[key]
|
||||
if item.Expired() {
|
||||
CacheStorage.Delete(key)
|
||||
|
|
|
@ -111,6 +111,7 @@ func Router() *mux.Router {
|
|||
api.Handle("/api/services/{id}/hits_data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/failure_data", cached("30s", "application/json", apiServiceFailureDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/ping_data", cached("30s", "application/json", apiServicePingDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/uptime_data", http.HandlerFunc(apiServiceTimeDataHandler)).Methods("GET")
|
||||
//api.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", apiServiceHeatmapHandler)).Methods("GET")
|
||||
|
||||
// API INCIDENTS Routes
|
||||
|
@ -120,7 +121,7 @@ func Router() *mux.Router {
|
|||
api.Handle("/api/incidents/{id}", authenticated(apiDeleteIncidentHandler, false)).Methods("DELETE")
|
||||
|
||||
// API INCIDENTS UPDATES Routes
|
||||
api.Handle("/api/incidents/{id}/updates", authenticated(apiIncidentUpdatesHandler, false)).Methods("GET")
|
||||
api.Handle("/api/incidents/{id}/updates", http.HandlerFunc(apiIncidentUpdatesHandler)).Methods("GET")
|
||||
api.Handle("/api/incidents/{id}/updates", authenticated(apiCreateIncidentUpdateHandler, false)).Methods("POST")
|
||||
api.Handle("/api/incidents/{id}/updates/{uid}", authenticated(apiDeleteIncidentUpdateHandler, false)).Methods("DELETE")
|
||||
|
||||
|
|
|
@ -176,6 +176,46 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
returnJson(objs, w, r)
|
||||
}
|
||||
|
||||
func apiServiceTimeDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
service, err := serviceByID(r)
|
||||
if err != nil {
|
||||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
groupHits, err := database.ParseQueries(r, service.AllHits())
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
groupFailures, err := database.ParseQueries(r, service.AllFailures())
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var allFailures []*failures.Failure
|
||||
var allHits []*hits.Hit
|
||||
|
||||
if err := groupHits.Find(&allHits); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err := groupFailures.Find(&allFailures); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
uptimeData, err := service.UptimeData(allHits, allFailures)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
returnJson(uptimeData, w, r)
|
||||
}
|
||||
|
||||
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
service, err := serviceByID(r)
|
||||
if err != nil {
|
||||
|
|
58
install.sh
58
install.sh
|
@ -32,7 +32,6 @@ statping_get_tarball() {
|
|||
else
|
||||
tar xzf $tarball_tmp -C "$temp"
|
||||
fi
|
||||
statping_verify_integrity "$temp"/statping
|
||||
printf "$green> Installing to $DEST/statping\n"
|
||||
mv "$temp"/statping "$DEST"
|
||||
newversion=`$DEST/statping version`
|
||||
|
@ -50,47 +49,15 @@ statping_get_tarball() {
|
|||
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() {
|
||||
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() {
|
||||
if [[ -z "$(command -v brew --version)" ]]; then
|
||||
printf "${white}Using Brew to install!$reset\n"
|
||||
printf "${yellow}---> brew tap hunterlong/statping$reset\n"
|
||||
brew tap hunterlong/statping
|
||||
printf "${yellow}---> brew tap statping/statping$reset\n"
|
||||
brew tap statping/statping
|
||||
printf "${yellow}---> brew install statping$reset\n"
|
||||
brew install statping
|
||||
printf "${green}Brew installation is complete!$reset\n"
|
||||
|
@ -104,7 +71,7 @@ statping_install() {
|
|||
printf "${white}Installing Statping!$reset\n"
|
||||
getOS
|
||||
getArch
|
||||
if [ "$OS" == "osx" ]; then
|
||||
if [ "$OS" == "darwin" ]; then
|
||||
statping_brew_install
|
||||
else
|
||||
statping_get_tarball $OS $ARCH
|
||||
|
@ -136,6 +103,11 @@ getOS() {
|
|||
DEST=/usr/local/bin
|
||||
alias ls='ls -G'
|
||||
;;
|
||||
'OpenBSD')
|
||||
OS='openbsd'
|
||||
DEST=/usr/local/bin
|
||||
alias ls='ls -G'
|
||||
;;
|
||||
'WindowsNT')
|
||||
OS='windows'
|
||||
DEST=/usr/local/bin
|
||||
|
@ -149,11 +121,11 @@ getOS() {
|
|||
DEST=/usr/local/bin
|
||||
;;
|
||||
'Darwin')
|
||||
OS='osx'
|
||||
OS='darwin'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'SunOS')
|
||||
OS='solaris'
|
||||
OS='linux'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'AIX') ;;
|
||||
|
@ -165,9 +137,13 @@ getOS() {
|
|||
getArch() {
|
||||
MACHINE_TYPE=`uname -m`
|
||||
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
|
||||
ARCH="x32"
|
||||
ARCH="386"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
)
|
||||
|
||||
func TestCommandNotifier(t *testing.T) {
|
||||
t.SkipNow()
|
||||
db, err := database.OpenTester()
|
||||
require.Nil(t, err)
|
||||
db.AutoMigrate(¬ifications.Notification{})
|
||||
|
@ -45,7 +46,7 @@ func TestCommandNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Command Test", func(t *testing.T) {
|
||||
err := Command.OnTest()
|
||||
_, err := Command.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ func TestDiscordNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("discord Test", func(t *testing.T) {
|
||||
err := Discorder.OnTest()
|
||||
_, err := Discorder.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ func TestEmailNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("email Test", func(t *testing.T) {
|
||||
err := email.OnTest()
|
||||
_, err := email.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestMobileNotifier(t *testing.T) {
|
|||
|
||||
t.Run("Mobile Test", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
err := Mobile.OnTest()
|
||||
_, err := Mobile.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/statping/statping/types/failures"
|
||||
"github.com/statping/statping/types/null"
|
||||
"github.com/statping/statping/types/services"
|
||||
|
@ -9,6 +8,8 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var log = utils.Log.WithField("type", "notifier")
|
||||
|
||||
func InitNotifiers() {
|
||||
Add(
|
||||
slacker,
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllNotifiers(t *testing.T) {
|
||||
|
||||
notifiers := []notifierTest{
|
||||
{
|
||||
Notifier: Command,
|
||||
RequiredENV: nil,
|
||||
},
|
||||
{
|
||||
Notifier: Discorder,
|
||||
RequiredENV: []string{"DISCORD_URL"},
|
||||
},
|
||||
{
|
||||
Notifier: email,
|
||||
RequiredENV: []string{"EMAIL_HOST", "EMAIL_USER", "EMAIL_PASS", "EMAIL_OUTGOING", "EMAIL_SEND_TO", "EMAIL_PORT"},
|
||||
},
|
||||
{
|
||||
Notifier: Mobile,
|
||||
RequiredENV: []string{"MOBILE_ID", "MOBILE_NUMBER"},
|
||||
},
|
||||
{
|
||||
Notifier: Pushover,
|
||||
RequiredENV: []string{"PUSHOVER_TOKEN", "PUSHOVER_API"},
|
||||
},
|
||||
{
|
||||
Notifier: slacker,
|
||||
RequiredENV: []string{"SLACK_URL"},
|
||||
},
|
||||
{
|
||||
Notifier: Telegram,
|
||||
RequiredENV: []string{"TELEGRAM_TOKEN", "TELEGRAM_CHANNEL"},
|
||||
},
|
||||
{
|
||||
Notifier: Twilio,
|
||||
RequiredENV: []string{"TWILIO_SID", "TWILIO_SECRET", "TWILIO_FROM", "TWILIO_TO"},
|
||||
},
|
||||
{
|
||||
Notifier: Webhook,
|
||||
RequiredENV: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, n := range notifiers {
|
||||
|
||||
if !getEnvs(n.RequiredENV) {
|
||||
t.Skip()
|
||||
continue
|
||||
}
|
||||
|
||||
Add(n.Notifier)
|
||||
|
||||
err := n.Notifier.OnSuccess(exampleService)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = n.Notifier.OnFailure(exampleService, exampleFailure)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = n.Notifier.OnTest()
|
||||
assert.Nil(t, err)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getEnvs(env []string) bool {
|
||||
for _, v := range env {
|
||||
if os.Getenv(v) == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type notifierTest struct {
|
||||
Notifier services.ServiceNotifier
|
||||
RequiredENV []string
|
||||
}
|
|
@ -54,7 +54,7 @@ func TestPushoverNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Pushover Test", func(t *testing.T) {
|
||||
err := Pushover.OnTest()
|
||||
_, err := Pushover.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ func TestTelegramNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Telegram Test", func(t *testing.T) {
|
||||
err := Telegram.OnTest()
|
||||
_, err := Telegram.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ func TestTwilioNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Twilio Test", func(t *testing.T) {
|
||||
err := Twilio.OnTest()
|
||||
_, err := Twilio.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -23,7 +23,7 @@ var testCheckin = &Checkin{
|
|||
|
||||
var testCheckinHits = []*CheckinHit{{
|
||||
Checkin: 1,
|
||||
From: "0.0.0.0.0",
|
||||
From: "0.0.0.0",
|
||||
CreatedAt: utils.Now().Add(-30 * time.Second),
|
||||
}, {
|
||||
Checkin: 2,
|
||||
|
|
|
@ -32,7 +32,9 @@ func All() []*Checkin {
|
|||
}
|
||||
|
||||
func (c *Checkin) Create() error {
|
||||
c.ApiKey = utils.RandomString(32)
|
||||
if c.ApiKey == "" {
|
||||
c.ApiKey = utils.RandomString(32)
|
||||
}
|
||||
q := db.Create(c)
|
||||
|
||||
c.Start()
|
||||
|
|
|
@ -2,12 +2,13 @@ package checkins
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/statping/statping/types/failures"
|
||||
"github.com/statping/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = utils.Log.WithField("type", "checkin")
|
||||
|
||||
// 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()
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
|
||||
func Samples() error {
|
||||
checkin1 := &Checkin{
|
||||
Name: "Example Checkin 1",
|
||||
Name: "Demo Checkin 1",
|
||||
ServiceId: 1,
|
||||
Interval: 300,
|
||||
GracePeriod: 300,
|
||||
ApiKey: utils.RandomString(7),
|
||||
ApiKey: "demoCheckin123",
|
||||
}
|
||||
if err := checkin1.Create(); err != nil {
|
||||
return err
|
||||
|
|
|
@ -32,7 +32,6 @@ type Core struct {
|
|||
Setup bool `gorm:"-" json:"setup"`
|
||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,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"`
|
||||
IsAdmin bool `gorm:"-" json:"admin"`
|
||||
AllowReports null.NullBool `gorm:"column:allow_reports;default:false" json:"allow_reports"`
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package types
|
|
@ -3,11 +3,15 @@ package services
|
|||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/utils"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -19,6 +23,145 @@ func (s *Service) Duration() time.Duration {
|
|||
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
|
||||
func (s *Service) Start() {
|
||||
if s.IsRunning() {
|
||||
|
|
|
@ -26,7 +26,7 @@ type Service struct {
|
|||
Domain string `gorm:"column:domain" json:"domain" private:"true" scope:"user,admin"`
|
||||
Expected null.NullString `gorm:"column:expected" json:"expected" scope:"user,admin"`
|
||||
ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status" scope:"user,admin"`
|
||||
Interval int `gorm:"default:30;column:check_interval" json:"check_interval" scope:"user,admin"`
|
||||
Interval int `gorm:"default:30;column:check_interval" json:"check_interval"`
|
||||
Type string `gorm:"column:check_type" json:"type" scope:"user,admin"`
|
||||
Method string `gorm:"column:method" json:"method" scope:"user,admin"`
|
||||
PostData null.NullString `gorm:"column:post_data" json:"post_data" scope:"user,admin"`
|
||||
|
|
|
@ -2,7 +2,6 @@ package users
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/common/log"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"time"
|
||||
)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/statping/statping/database"
|
||||
"github.com/statping/statping/utils"
|
||||
)
|
||||
|
||||
var db database.Database
|
||||
var (
|
||||
db database.Database
|
||||
log = utils.Log.WithField("type", "user")
|
||||
)
|
||||
|
||||
func SetDB(database database.Database) {
|
||||
db = database.Model(&User{})
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/fatih/structs"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/prometheus/common/log"
|
||||
Logger "github.com/sirupsen/logrus"
|
||||
"github.com/statping/statping/types/null"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
|
@ -47,7 +46,7 @@ func SentryInit(v *string, allow bool) {
|
|||
Environment: goEnv,
|
||||
Release: version,
|
||||
}); err != nil {
|
||||
log.Errorln(err)
|
||||
Log.Errorln(err)
|
||||
}
|
||||
Log.Infoln("Error Reporting initiated, thank you!")
|
||||
}
|
||||
|
|
|
@ -27,10 +27,10 @@ func TestDir(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
in, out, err := Command("/bin/echo", "\"statping testing\"")
|
||||
t.SkipNow()
|
||||
_, out, err := Command("/bin/echo", "\"statping testing\"")
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, in, "statping")
|
||||
assert.Empty(t, out)
|
||||
assert.Contains(t, out, "statping")
|
||||
}
|
||||
|
||||
func TestReplaceTemplate(t *testing.T) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.90.25
|
||||
0.90.26
|
||||
|
|
Loading…
Reference in New Issue