mirror of https://github.com/statping/statping
commit
977013550a
|
@ -21,6 +21,12 @@ jobs:
|
|||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.18.2'
|
||||
- name: Configure AWS credentials from account
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-west-2
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Add GOBIN to PATH
|
||||
|
@ -29,6 +35,13 @@ jobs:
|
|||
echo ::set-env name=VERSION::$(cat version.txt)
|
||||
shell: bash
|
||||
|
||||
- name: Font Awesome authentication
|
||||
env:
|
||||
FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOME_TOKEN }}
|
||||
run: |
|
||||
npm config set "@fortawesome:registry" https://npm.fontawesome.com/
|
||||
npm config set "//npm.fontawesome.com/:_authToken" $FONTAWESOME_TOKEN
|
||||
|
||||
- name: Install Global Dependencies
|
||||
run: npm install -g yarn sass cross-env
|
||||
|
||||
|
@ -53,6 +66,12 @@ jobs:
|
|||
name: static-rice-box
|
||||
path: ./source
|
||||
|
||||
- name: Upload Assets to S3
|
||||
run: |
|
||||
tar -czvf source.tar.gz source/
|
||||
aws s3 cp source.tar.gz s3://assets.statping.com/
|
||||
rm -rf source.tar.gz
|
||||
|
||||
test:
|
||||
needs: compile
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -116,6 +135,7 @@ jobs:
|
|||
gotestsum --no-summary=skipped --format dots -- -covermode=count -coverprofile=coverage.out -p=1 ./...
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
DB_CONN: sqlite3
|
||||
STATPING_DIR: ${{ github.workspace }}
|
||||
API_SECRET: demopassword123
|
||||
|
@ -231,6 +251,8 @@ jobs:
|
|||
- name: Install Statping
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
MJML_APP: ${{ secrets.MJML_APP }}
|
||||
MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }}
|
||||
run: |
|
||||
make build
|
||||
chmod +x statping
|
||||
|
@ -287,6 +309,7 @@ jobs:
|
|||
- name: Install Statping
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: |
|
||||
make build
|
||||
chmod +x statping
|
||||
|
@ -359,7 +382,9 @@ jobs:
|
|||
- name: Build Binaries
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: $GITHUB_SHA
|
||||
COMMIT: ${{ github.sha }}
|
||||
MJML_APP: ${{ secrets.MJML_APP }}
|
||||
MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }}
|
||||
run: make build-folders build-linux build-linux-arm build-darwin build-win compress-folders
|
||||
|
||||
docker-release:
|
||||
|
@ -392,7 +417,13 @@ jobs:
|
|||
buildx-docker-master
|
||||
|
||||
- name: Docker Build :base
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: make buildx-base
|
||||
|
||||
- name: Docker Build :dev
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: make buildx-dev
|
||||
|
|
|
@ -16,6 +16,12 @@ jobs:
|
|||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.18.2'
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-west-2
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Add GOBIN to PATH
|
||||
|
@ -24,6 +30,13 @@ jobs:
|
|||
echo ::set-env name=VERSION::$(cat version.txt)
|
||||
shell: bash
|
||||
|
||||
- name: Font Awesome authentication
|
||||
env:
|
||||
FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOME_TOKEN }}
|
||||
run: |
|
||||
npm config set "@fortawesome:registry" https://npm.fontawesome.com/
|
||||
npm config set "//npm.fontawesome.com/:_authToken" $FONTAWESOME_TOKEN
|
||||
|
||||
- name: Install Global Dependencies
|
||||
run: npm install -g yarn sass cross-env
|
||||
|
||||
|
@ -48,6 +61,19 @@ jobs:
|
|||
name: static-rice-box
|
||||
path: ./source
|
||||
|
||||
- name: Upload Assets to S3
|
||||
run: |
|
||||
tar -czvf source.tar.gz source/
|
||||
cp source.tar.gz source-${VERSION}.tar.gz
|
||||
aws s3 cp source.tar.gz s3://assets.statping.com/
|
||||
aws s3 cp source-${VERSION}.tar.gz s3://assets.statping.com/
|
||||
rm -rf source.tar.gz
|
||||
rm -rf source-${VERSION}.tar.gz
|
||||
aws s3 cp source/dist/css/ s3://assets.statping.com/css/ --recursive
|
||||
aws s3 cp source/dist/js/ s3://assets.statping.com/js/ --recursive
|
||||
aws s3 cp source/dist/scss/ s3://assets.statping.com/scss/ --recursive
|
||||
aws s3 cp install.sh s3://assets.statping.com/
|
||||
|
||||
test:
|
||||
needs: compile
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -226,6 +252,8 @@ jobs:
|
|||
- name: Install Statping
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
MJML_APP: ${{ secrets.MJML_APP }}
|
||||
MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }}
|
||||
run: |
|
||||
make build
|
||||
chmod +x statping
|
||||
|
@ -355,6 +383,8 @@ jobs:
|
|||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: $GITHUB_SHA
|
||||
MJML_APP: ${{ secrets.MJML_APP }}
|
||||
MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }}
|
||||
run: make build-folders build-linux build-linux-arm build-darwin build-win compress-folders
|
||||
|
||||
- name: Upload Builds
|
||||
|
@ -434,9 +464,15 @@ jobs:
|
|||
buildx-docker
|
||||
|
||||
- name: Docker Build :base
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: make buildx-base
|
||||
|
||||
- name: Docker Build :lastest
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
run: make buildx-latest
|
||||
|
||||
sentry-release:
|
||||
|
|
|
@ -25,6 +25,13 @@ jobs:
|
|||
echo ::set-env name=VERSION::$(cat version.txt)
|
||||
shell: bash
|
||||
|
||||
- name: Font Awesome authentication
|
||||
env:
|
||||
FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOME_TOKEN }}
|
||||
run: |
|
||||
npm config set "@fortawesome:registry" https://npm.fontawesome.com/
|
||||
npm config set "//npm.fontawesome.com/:_authToken" $FONTAWESOME_TOKEN
|
||||
|
||||
- name: Install Global Dependencies
|
||||
run: npm install -g yarn sass cross-env
|
||||
|
||||
|
|
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,3 +1,18 @@
|
|||
# 0.90.62 (08-07-2020)
|
||||
- Added Notification logs
|
||||
- Fixed issues with Notifer After (x) failures for notifications
|
||||
- Modified notifications to not send on initial startup
|
||||
- Updated Incident UI
|
||||
- Added additional testing for notifications
|
||||
- Modified SCSS/SASS files to be generated from 1, main.scss to main.css
|
||||
- Modified index page to use /assets directory for assets, (main.css, style.css)
|
||||
- Modified index page to use CDN asset paths
|
||||
- Fixed New Checkin form
|
||||
- Modified email notifier template to be rendered from MJML (using go generate)
|
||||
- Modified database relationships with services using gorm
|
||||
- Modified "statping env" command to show user/group ID
|
||||
- Removed "js" folder when exporting assets, js files are always version of release, not static JS files
|
||||
|
||||
# 0.90.61 (07-22-2020)
|
||||
- Modified sass layouts, organized and split up sections
|
||||
- Modified Checkins to seconds rather than milliseconds (for cronjob)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "com.statping",
|
||||
"title": "Statping",
|
||||
"author": "Hunter Long <info@statping.com>",
|
||||
"description": "Monitor your web services and remote servers",
|
||||
"tagline": "Server Monitoring",
|
||||
"version": "0.90.61",
|
||||
"healthCheckPath": "/health",
|
||||
"httpPort": 8080,
|
||||
"addons": {
|
||||
"localstorage": {}
|
||||
},
|
||||
"manifestVersion": 2,
|
||||
"website": "https://github.com/statping/statping",
|
||||
"contactEmail": "info@statping.com",
|
||||
"icon": "https://assets.statping.com/icon.png",
|
||||
"tags": [ "monitoring", "uptime" ],
|
||||
"mediaLinks": [ "https://assets.statping.com/cloudron.png" ]
|
||||
}s
|
|
@ -13,6 +13,7 @@ RUN yarn build && yarn cache clean
|
|||
FROM golang:1.14-alpine AS backend
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
ARG VERSION
|
||||
ARG COMMIT
|
||||
ARG BUILDPLATFORM
|
||||
ARG TARGETARCH
|
||||
RUN apk add --update --no-cache libstdc++ gcc g++ make git autoconf \
|
||||
|
@ -36,8 +37,8 @@ RUN go get github.com/stretchr/testify/assert && \
|
|||
go get github.com/crazy-max/xgo
|
||||
COPY . .
|
||||
COPY --from=frontend /statping/dist/ ./source/dist/
|
||||
RUN make clean frontend-copy generate embed
|
||||
RUN GOOS=linux GOARCH=$TARGETARCH go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o statping --tags "netgo linux" ./cmd
|
||||
RUN make clean generate embed
|
||||
RUN go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo linux" ./cmd
|
||||
RUN chmod a+x statping && mv statping /go/bin/statping
|
||||
# /go/bin/statping - statping binary
|
||||
# /root/sassc/bin/sassc - sass binary
|
||||
|
|
64
Makefile
64
Makefile
|
@ -1,11 +1,12 @@
|
|||
VERSION=$(shell cat version.txt)
|
||||
COMMIT=$(shell git rev-parse HEAD)
|
||||
SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278
|
||||
BINARY_NAME=statping
|
||||
GOBUILD=go build -a
|
||||
GOVERSION=1.14.0
|
||||
NODE_VERSION=12.18.2
|
||||
XGO=xgo -go $(GOVERSION) --dest=build
|
||||
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION}"
|
||||
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}"
|
||||
TRVIS_SECRET=O3/2KTOV8krv+yZ1EB/7D1RQRe6NdpFUEJNJkMS/ollYqmz3x2mCO7yIgIJKCKguLXZxjM6CxJcjlCrvUwibL+8BBp7xJe4XFIOrjkPvbbVPry4HkFZCf2GfcUK6o4AByQ+RYqsW2F17Fp9KLQ1rL3OT3eLTwCAGKx3tlY8y+an43zkmo5dN64V6sawx26fh6XTfww590ey+ltgQTjf8UPNup2wZmGvMo9Hwvh/bYR/47bR6PlBh6vhlKWyotKf2Fz1Bevbu0zc35pee5YlsrHR+oSF+/nNd/dOij34BhtqQikUR+zQVy9yty8SlmneVwD3yOENvlF+8roeKIXb6P6eZnSMHvelhWpAFTwDXq2N3d/FIgrQtLxsAFTI3nTHvZgs6OoTd6dA0wkhuIGLxaL3FOeztCdxP5J/CQ9GUcTvifh5ArGGwYxRxQU6rTgtebJcNtXFISP9CEUR6rwRtb6ax7h6f1SbjUGAdxt+r2LbEVEk4ZlwHvdJ2DtzJHT5DQtLrqq/CTUgJ8SJFMkrJMp/pPznKhzN4qvd8oQJXygSXX/gz92MvoX0xgpNeLsUdAn+PL9KketfR+QYosBz04d8k05E+aTqGaU7FUCHPTLwlOFvLD8Gbv0zsC/PWgSLXTBlcqLEz5PHwPVHTcVzspKj/IyYimXpCSbvu1YOIjyc=
|
||||
PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }'
|
||||
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "merge_mode": "replace", "language": "go", "go": 1.14, "install": true, "sudo": "required", "services": ["docker"], "env": { "secure": "${TRVIS_SECRET}" }, "before_deploy": ["git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [{ "provider": "releases", "api_key": "$$GITHUB_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": { "branch": "master" } }], "before_script": ["rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install stable", "nvm install 10.17.0", "nvm use 10.17.0 --default", "npm install -g sass yarn cross-env", "pip install --user awscli"], "script": ["make release"], "after_success": [], "after_deploy": ["make post-release"] } } }'
|
||||
|
@ -17,10 +18,23 @@ ARCHS = 386 arm amd64 arm64
|
|||
all: build-deps compile install test build
|
||||
|
||||
test: clean compile
|
||||
go test -v -p=1 -ldflags="-X main.VERSION=0.99.99" -coverprofile=coverage.out ./...
|
||||
go test -v -p=1 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -coverprofile=coverage.out ./...
|
||||
|
||||
build: clean
|
||||
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o statping --tags "netgo linux" ./cmd
|
||||
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo linux" ./cmd
|
||||
|
||||
go-build: clean
|
||||
rm -rf source/dist
|
||||
rm -rf source/rice-box.go
|
||||
wget https://assets.statping.com/source.tar.gz
|
||||
tar -xvf source.tar.gz
|
||||
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo" ./cmd
|
||||
|
||||
lint:
|
||||
go fmt ./...
|
||||
golint ./...
|
||||
impi --local github.com/statping/statping/ --scheme stdLocalThirdParty ./...
|
||||
goimports ./...
|
||||
|
||||
up:
|
||||
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans
|
||||
|
@ -54,7 +68,7 @@ cypress: clean
|
|||
|
||||
test-api:
|
||||
DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none statping &
|
||||
sleep 5000 && newman run source/tmpl/postman.json -e dev/postman_environment.json --delay-request 500
|
||||
sleep 5000 && newman run dev/postman.json -e dev/postman_environment.json --delay-request 500
|
||||
|
||||
test-deps:
|
||||
go get golang.org/x/tools/cmd/cover
|
||||
|
@ -129,14 +143,14 @@ frontend-build:
|
|||
@rm -rf source/dist && rm -rf frontend/dist
|
||||
@echo "yarn install and build static frontend"
|
||||
cd frontend && yarn && yarn build
|
||||
@cp -r frontend/dist source/ && cp -r frontend/src/assets/scss source/dist/
|
||||
@cp -r source/tmpl/*.* source/dist/
|
||||
@cp -r frontend/dist source/
|
||||
@cp -r frontend/src/assets/scss source/dist/
|
||||
@cp frontend/public/favicon.ico source/dist/
|
||||
@cp frontend/public/robots.txt source/dist/
|
||||
@cp frontend/public/banner.png source/dist/
|
||||
@cp -r frontend/public/favicon source/dist/
|
||||
@echo "Frontend build complete at ./source/dist"
|
||||
|
||||
frontend-copy:
|
||||
cp -r source/tmpl/*.* source/dist/
|
||||
|
||||
yarn:
|
||||
rm -rf source/dist && rm -rf frontend/dist
|
||||
cd frontend && yarn
|
||||
|
@ -145,6 +159,7 @@ yarn:
|
|||
compile: frontend-build
|
||||
rm -f source/rice-box.go
|
||||
cd source && rice embed-go
|
||||
make generate
|
||||
|
||||
embed:
|
||||
cd source && rice embed-go
|
||||
|
@ -155,8 +170,12 @@ install: build
|
|||
install-local: build
|
||||
mv $(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
|
||||
|
||||
install-darwin:
|
||||
go build -a -ldflags "-X main.VERSION=${VERSION}" -o statping --tags "netgo darwin" ./cmd
|
||||
mv $(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
|
||||
|
||||
generate:
|
||||
cd source && go generate
|
||||
go generate ./...
|
||||
|
||||
build-all: clean compile build-folders build-linux build-linux-arm build-darwin build-win compress-folders
|
||||
|
||||
|
@ -291,11 +310,14 @@ post-release: frontend-build upload_to_s3 publish-homebrew dockerhub
|
|||
publish-homebrew:
|
||||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/statping%2Fhomebrew-statping/requests
|
||||
|
||||
upload_to_s3: travis_s3_creds
|
||||
aws s3 cp ./source/dist/css $(ASSETS_BKT) --recursive --exclude "*" --include "*.css"
|
||||
aws s3 cp ./source/dist/js $(ASSETS_BKT) --recursive --exclude "*" --include "*.js"
|
||||
aws s3 cp ./source/dist/scss $(ASSETS_BKT) --recursive --exclude "*" --include "*.scss"
|
||||
aws s3 cp ./install.sh $(ASSETS_BKT)
|
||||
upload_to_s3:
|
||||
tar -czvf source.tar.gz source/
|
||||
aws s3 cp source.tar.gz s3://assets.statping.com/
|
||||
rm -rf source.tar.gz
|
||||
aws s3 cp source/dist/css/ s3://assets.statping.com/css/ --recursive --exclude "*" --include "*.css"
|
||||
aws s3 cp source/dist/js/ s3://assets.statping.com/js/ --recursive --exclude "*" --include "*.js"
|
||||
aws s3 cp source/dist/scss/ s3://assets.statping.com/scss/ --recursive --exclude "*" --include "*.scss"
|
||||
aws s3 cp install.sh s3://assets.statping.com/
|
||||
|
||||
travis_s3_creds:
|
||||
mkdir -p ~/.aws
|
||||
|
@ -357,25 +379,29 @@ certs:
|
|||
buildx-latest: multiarch
|
||||
docker buildx create --name statping-latest
|
||||
docker buildx inspect --builder statping-latest --bootstrap
|
||||
docker buildx build --builder statping-latest --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile -t statping/statping:latest -t statping/statping:v${VERSION} --build-arg=VERSION=${VERSION} .
|
||||
docker buildx build --builder statping-latest --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile -t statping/statping:latest -t statping/statping:v${VERSION} --build-arg=VERSION=${VERSION} --build-arg=COMMIT=${COMMIT} .
|
||||
docker buildx rm statping-latest
|
||||
|
||||
buildx-dev: multiarch
|
||||
docker buildx create --name statping-dev
|
||||
docker buildx inspect --builder statping-dev --bootstrap
|
||||
docker buildx build --builder statping-dev --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile -t statping/statping:dev --build-arg=VERSION=${VERSION} .
|
||||
docker buildx build --builder statping-dev --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile -t statping/statping:dev --build-arg=VERSION=${VERSION} --build-arg=COMMIT=${COMMIT} .
|
||||
docker buildx rm statping-dev
|
||||
|
||||
buildx-base: multiarch
|
||||
docker buildx create --name statping-base
|
||||
docker buildx inspect --builder statping-base --bootstrap
|
||||
docker buildx build --builder statping-base --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile.base -t statping/statping:base --build-arg=VERSION=${VERSION} .
|
||||
docker buildx build --builder statping-base --cache-from "type=local,src=/tmp/.buildx-cache" --cache-to "type=local,dest=/tmp/.buildx-cache" --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -f Dockerfile.base -t statping/statping:base --build-arg=VERSION=${VERSION} --build-arg=COMMIT=${COMMIT} .
|
||||
docker buildx rm statping-base
|
||||
|
||||
multiarch:
|
||||
mkdir /tmp/.buildx-cache || true
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
delve:
|
||||
go build -gcflags "all=-N -l" -o statping ./cmd
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./statping
|
||||
|
||||
check:
|
||||
@echo "Checking the programs required for the build are installed..."
|
||||
@echo "go: $(shell go version) - $(shell which go)" && go version >/dev/null 2>&1 || (echo "ERROR: go 1.14 is required."; exit 1)
|
||||
|
@ -383,5 +409,5 @@ check:
|
|||
@echo "yarn: $(shell yarn --version) - $(shell which yarn)" && yarn --version >/dev/null 2>&1 || (echo "ERROR: yarn is required."; exit 1)
|
||||
@echo "All required programs are installed!"
|
||||
|
||||
.PHONY: all check build certs multiarch build-all buildx-base buildx-dev buildx-latest build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-linux build-mac build-win build-all postman
|
||||
.PHONY: all check build certs multiarch install-darwin go-build build-all buildx-base buildx-dev buildx-latest build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-linux build-mac build-win build-all postman
|
||||
.SILENT: travis_s3_creds
|
||||
|
|
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/statping/statping/master/source/tmpl/banner.png",
|
||||
"logo": "https://assets.statping.com/banner.png",
|
||||
"keywords": ["statping", "server", "monitoring", "status page","golang", "go"]
|
||||
}
|
||||
|
|
77
cmd/cli.go
77
cmd/cli.go
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/statping/statping/utils"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -34,13 +35,77 @@ func assetsCli() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func systemctlCli() error {
|
||||
fmt.Println("still in the works...")
|
||||
func systemctlCli(dir string, uninstall bool, port int64) error {
|
||||
location := "/etc/systemd/system/statping.service"
|
||||
|
||||
if uninstall {
|
||||
fmt.Println("systemctl stop statping")
|
||||
if _, _, err := utils.Command("systemctl", "stop", "statping"); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
fmt.Println("systemctl disable statping")
|
||||
if _, _, err := utils.Command("systemctl", "disable", "statping"); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
fmt.Println("Deleting systemctl: ", location)
|
||||
if err := utils.DeleteFile(location); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if ok := utils.FolderExists(dir); !ok {
|
||||
return errors.New("directory does not exist: " + dir)
|
||||
}
|
||||
|
||||
binPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := []byte(`[Unit]
|
||||
Description=Statping Server
|
||||
After=network.target
|
||||
After=systemd-user-sessions.service
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
Environment="STATPING_DIR=` + dir + `"
|
||||
Environment="ALLOW_REPORTS=true"
|
||||
ExecStart=` + binPath + ` --port=` + utils.ToString(port) + `
|
||||
WorkingDirectory=` + dir + `
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target"
|
||||
`)
|
||||
fmt.Println("Saving systemctl service to: ", location)
|
||||
fmt.Printf("Using directory %s for Statping data\n", dir)
|
||||
fmt.Printf("Running on port %d\n", port)
|
||||
fmt.Printf("\n\n%s\n\n", string(config))
|
||||
if err := utils.SaveFile(location, config); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("systemctl daemon-reload")
|
||||
if _, _, err := utils.Command("systemctl", "daemon-reload"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("systemctl enable statping")
|
||||
if _, _, err := utils.Command("systemctl", "enable", "statping.service"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("systemctl start statping")
|
||||
if _, _, err := utils.Command("systemctl", "start", "statping"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Statping was will auto start on reboots")
|
||||
fmt.Println("systemctl service: ", location)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportCli(args []string) error {
|
||||
filename := fmt.Sprintf("%s/statping-%s.json", utils.Directory, time.Now().Format("01-02-2006-1504"))
|
||||
filename := filepath.Join(utils.Directory, time.Now().Format("01-02-2006-1504")+".json")
|
||||
if len(args) == 1 {
|
||||
filename = fmt.Sprintf("%s/%s", utils.Directory, args)
|
||||
}
|
||||
|
@ -78,7 +143,7 @@ func sassCli() error {
|
|||
if err := source.Assets(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := source.CompileSASS(source.DefaultScss...); err != nil {
|
||||
if err := source.CompileSASS(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -133,6 +198,10 @@ func resetCli() error {
|
|||
|
||||
func envCli() error {
|
||||
fmt.Println("Statping Configuration")
|
||||
fmt.Printf("Process ID: %d\n", os.Getpid())
|
||||
fmt.Printf("Running as user id: %d\n", os.Getuid())
|
||||
fmt.Printf("Running as group id: %d\n", os.Getgid())
|
||||
fmt.Printf("Statping Directory: %s\n", utils.Directory)
|
||||
for k, v := range utils.Params.AllSettings() {
|
||||
fmt.Printf("%s=%v\n", strings.ToUpper(k), v)
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/statping/statping/source"
|
||||
"github.com/statping/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -18,16 +20,15 @@ func init() {
|
|||
}
|
||||
|
||||
func TestStatpingDirectory(t *testing.T) {
|
||||
dir := utils.Directory
|
||||
require.NotContains(t, dir, "/cmd")
|
||||
require.NotEmpty(t, dir)
|
||||
|
||||
dir = utils.Params.GetString("STATPING_DIR")
|
||||
require.NotContains(t, dir, "/cmd")
|
||||
require.NotEmpty(t, dir)
|
||||
}
|
||||
|
||||
func TestEnvCLI(t *testing.T) {
|
||||
os.Setenv("API_SECRET", "demoapisecret123")
|
||||
os.Setenv("SASS", "/usr/local/bin/sass")
|
||||
|
||||
cmd := rootCmd
|
||||
b := bytes.NewBufferString("")
|
||||
cmd.SetOut(b)
|
||||
|
@ -39,6 +40,12 @@ func TestEnvCLI(t *testing.T) {
|
|||
assert.Contains(t, string(out), VERSION)
|
||||
assert.Contains(t, utils.Directory, string(out))
|
||||
assert.Contains(t, "SAMPLE_DATA=true", string(out))
|
||||
assert.Contains(t, "API_SECRET=demoapisecret123", string(out))
|
||||
assert.Contains(t, "STATPING_DIR="+dir, string(out))
|
||||
assert.Contains(t, "SASS=/usr/local/bin/sass", string(out))
|
||||
|
||||
os.Unsetenv("API_SECRET")
|
||||
os.Unsetenv("SASS")
|
||||
}
|
||||
|
||||
func TestVersionCLI(t *testing.T) {
|
||||
|
@ -58,16 +65,26 @@ func TestAssetsCLI(t *testing.T) {
|
|||
b := bytes.NewBufferString("")
|
||||
cmd.SetOut(b)
|
||||
cmd.SetArgs([]string{"assets"})
|
||||
cmd.Execute()
|
||||
err := cmd.Execute()
|
||||
require.Nil(t, err)
|
||||
out, err := ioutil.ReadAll(b)
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, string(out), VERSION)
|
||||
assert.FileExists(t, utils.Directory+"/assets/css/main.css")
|
||||
assert.FileExists(t, utils.Directory+"/assets/css/style.css")
|
||||
assert.FileExists(t, utils.Directory+"/assets/css/vendor.css")
|
||||
assert.FileExists(t, utils.Directory+"/assets/scss/base.scss")
|
||||
assert.FileExists(t, utils.Directory+"/assets/scss/mobile.scss")
|
||||
assert.FileExists(t, utils.Directory+"/assets/scss/variables.scss")
|
||||
for _, f := range source.RequiredFiles {
|
||||
assert.FileExists(t, utils.Directory+"/assets/"+f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCLI(t *testing.T) {
|
||||
cmd := rootCmd
|
||||
b := bytes.NewBufferString("")
|
||||
cmd.SetOut(b)
|
||||
cmd.SetArgs([]string{"update"})
|
||||
err := cmd.Execute()
|
||||
require.Nil(t, err)
|
||||
out, err := ioutil.ReadAll(b)
|
||||
require.Nil(t, err)
|
||||
assert.Contains(t, string(out), VERSION)
|
||||
}
|
||||
|
||||
func TestHelpCLI(t *testing.T) {
|
||||
|
|
|
@ -5,24 +5,28 @@ import (
|
|||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/statping/statping/utils"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "statping",
|
||||
Short: "A simple Application Status Monitor that is opensource and lightweight.",
|
||||
Use: "statping",
|
||||
Version: VERSION,
|
||||
Short: "A simple Application Status Monitor that is opensource and lightweight.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
start()
|
||||
},
|
||||
}
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update to the latest version",
|
||||
Use: "update",
|
||||
Example: "statping update",
|
||||
Short: "Update to the latest version",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.Infoln("Updating Statping to the latest version...")
|
||||
log.Infoln("curl -o- -L https://statping.com/install.sh | bash")
|
||||
curl, err := exec.LookPath("curl")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -58,8 +62,9 @@ var updateCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of Statping",
|
||||
Use: "version",
|
||||
Example: "statping version",
|
||||
Short: "Print the version number of Statping",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if COMMIT != "" {
|
||||
fmt.Printf("%s (%s)\n", VERSION, COMMIT)
|
||||
|
@ -71,26 +76,37 @@ var versionCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var systemctlCmd = &cobra.Command{
|
||||
Use: "systemctl [install/uninstall]",
|
||||
Short: "Install or Uninstall systemctl links",
|
||||
Use: "systemctl [install/uninstall]",
|
||||
Example: "statping systemctl install",
|
||||
Short: "Install or Uninstall systemctl services",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := systemctlCli(); err != nil {
|
||||
if args[1] == "install" {
|
||||
if len(args) < 3 {
|
||||
return errors.New("requires 'install <working_path> <port>'")
|
||||
}
|
||||
}
|
||||
port := utils.ToInt(args[2])
|
||||
if port == 0 {
|
||||
port = 80
|
||||
}
|
||||
if err := systemctlCli(args[1], args[0] == "uninstall", port); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(0)
|
||||
return nil
|
||||
},
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("requires 'install' or 'uninstall' as arguments")
|
||||
if len(args) < 2 {
|
||||
return errors.New("requires 'install <working_path>' or 'uninstall' as arguments")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var assetsCmd = &cobra.Command{
|
||||
Use: "assets",
|
||||
Short: "Dump all assets used locally to be edited",
|
||||
Use: "assets",
|
||||
Example: "statping assets",
|
||||
Short: "Dump all assets used locally to be edited",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := assetsCli(); err != nil {
|
||||
return err
|
||||
|
@ -101,8 +117,9 @@ var assetsCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Exports your Statping settings to a 'statping-export.json' file.",
|
||||
Use: "export",
|
||||
Example: "statping export",
|
||||
Short: "Exports your Statping settings to a 'statping-export.json' file.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := exportCli(args); err != nil {
|
||||
return err
|
||||
|
@ -113,8 +130,9 @@ var exportCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var sassCmd = &cobra.Command{
|
||||
Use: "sass",
|
||||
Short: "Compile .scss files into the css directory",
|
||||
Use: "sass",
|
||||
Example: "statping sass",
|
||||
Short: "Compile .scss files into the css directory",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := sassCli(); err != nil {
|
||||
return err
|
||||
|
@ -125,8 +143,9 @@ var sassCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var envCmd = &cobra.Command{
|
||||
Use: "env",
|
||||
Short: "Return the configs that will be ran",
|
||||
Use: "env",
|
||||
Example: "statping env",
|
||||
Short: "Return the configs that will be ran",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := envCli(); err != nil {
|
||||
return err
|
||||
|
@ -137,8 +156,9 @@ var envCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var resetCmd = &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "Start a fresh copy of Statping",
|
||||
Use: "reset",
|
||||
Example: "statping reset",
|
||||
Short: "Start a fresh copy of Statping",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := resetCli(); err != nil {
|
||||
return err
|
||||
|
@ -149,8 +169,9 @@ var resetCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var onceCmd = &cobra.Command{
|
||||
Use: "once",
|
||||
Short: "Check all services 1 time and then quit",
|
||||
Use: "once",
|
||||
Example: "statping once",
|
||||
Short: "Check all services 1 time and then quit",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := onceCli(); err != nil {
|
||||
return err
|
||||
|
@ -161,8 +182,9 @@ var onceCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var importCmd = &cobra.Command{
|
||||
Use: "import [.json file]",
|
||||
Short: "Imports settings from a previously saved JSON file.",
|
||||
Use: "import [.json file]",
|
||||
Example: "statping import backup.json",
|
||||
Short: "Imports settings from a previously saved JSON file.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := importCli(args); err != nil {
|
||||
return err
|
||||
|
|
|
@ -39,7 +39,7 @@ type GroupQuery struct {
|
|||
}
|
||||
|
||||
func (b GroupQuery) Find(data interface{}) error {
|
||||
return b.db.Find(data).Error()
|
||||
return b.db.Order("id DESC").Find(data).Error()
|
||||
}
|
||||
|
||||
func (b GroupQuery) Database() Database {
|
||||
|
|
|
@ -3275,6 +3275,8 @@
|
|||
"pm.test(\"Check Login JWT Token\", function () {",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData).to.have.property('token');",
|
||||
" pm.expect(jsonData).to.have.property('admin');",
|
||||
" pm.globals.set(\"token\", jsonData.token);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
|
@ -3371,24 +3373,141 @@
|
|||
"_postman_previewlanguage": "json",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Length",
|
||||
"value": "174"
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Set-Cookie",
|
||||
"value": "statping_auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInNjb3BlcyI6ImFkbWluIiwiZXhwIjoxNTk2NzQzMDUzfQ.dQQGgUDhFEjCL2Gi-Seg0hBp_sqVsDn3cXB0GpSorJI; Path=/; Expires=Thu, 06 Aug 2020 19:44:13 GMT; Max-Age=259200"
|
||||
},
|
||||
{
|
||||
"key": "Date",
|
||||
"value": "Mon, 03 Aug 2020 19:44:13 GMT"
|
||||
},
|
||||
{
|
||||
"key": "Content-Length",
|
||||
"value": "197"
|
||||
},
|
||||
{
|
||||
"key": "Connection",
|
||||
"value": "close"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInNjb3BlcyI6ImFkbWluIiwiZXhwIjoxNTk2NzQzMDUzfQ.dQQGgUDhFEjCL2Gi-Seg0hBp_sqVsDn3cXB0GpSorJI\",\n \"admin\": true\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Check User Token",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "560e439b-d588-4a2f-a8a6-a0607531d74c",
|
||||
"exec": [
|
||||
"pm.test(\"Response is ok\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"View Token Response\", function () {",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.username).to.eql(\"admin\");",
|
||||
" pm.expect(jsonData.admin).to.eql(true);",
|
||||
" pm.expect(jsonData.scopes).to.eql(\"admin\");",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{api_key}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "urlencoded",
|
||||
"urlencoded": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{endpoint}}/api/users/token",
|
||||
"host": [
|
||||
"{{endpoint}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"token"
|
||||
]
|
||||
},
|
||||
"description": "Send your JWT token from login to this endpoint to return the JSON values."
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Check User Token",
|
||||
"originalRequest": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "urlencoded",
|
||||
"urlencoded": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{endpoint}}/api/users/token",
|
||||
"host": [
|
||||
"{{endpoint}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK",
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": "json",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Date",
|
||||
"value": "Sat, 02 May 2020 00:56:17 GMT"
|
||||
"value": "Mon, 03 Aug 2020 19:47:23 GMT"
|
||||
},
|
||||
{
|
||||
"key": "Set-Cookie",
|
||||
"value": "statping_auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsImV4cCI6MTU4ODY0MDE3N30.tf399_LfAphSGlKMtgphg6qpPrn-_w92XfCrK5FwbZY; Expires=Tue, 05 May 2020 00:56:17 GMT"
|
||||
"key": "Content-Length",
|
||||
"value": "68"
|
||||
},
|
||||
{
|
||||
"key": "Connection",
|
||||
"value": "close"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsImV4cCI6MTU4ODY0MDE3N30.tf399_LfAphSGlKMtgphg6qpPrn-_w92XfCrK5FwbZY\",\n \"admin\": true\n}"
|
||||
"body": "{\n \"username\": \"admin\",\n \"admin\": true,\n \"scopes\": \"admin\",\n \"exp\": 1596743053\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -60,6 +60,9 @@ const webpackConfig = merge(commonConfig, {
|
|||
plugins: [
|
||||
new webpack.EnvironmentPlugin(environment),
|
||||
new CleanWebpackPlugin(),
|
||||
// new webpack.optimize.LimitChunkCountPlugin({
|
||||
// maxChunks: 1
|
||||
// }),
|
||||
new MiniCSSExtractPlugin({
|
||||
filename: 'css/[name].css',
|
||||
chunkFilename: 'css/[name].css'
|
||||
|
|
|
@ -19,14 +19,12 @@
|
|||
"@fortawesome/free-brands-svg-icons": "^5.12.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.12.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||
"@sentry/browser": "^5.13.2",
|
||||
"@sentry/integrations": "^5.13.2",
|
||||
"@sentry/browser": "^5.20.1",
|
||||
"@sentry/integrations": "^5.20.1",
|
||||
"apexcharts": "^3.15.0",
|
||||
"axios": "^0.19.1",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.6.1",
|
||||
"codemirror-colorpicker": "^1.9.66",
|
||||
"core-js": "^3.4.4",
|
||||
"core-js": "^3.6.5",
|
||||
"date-fns": "^2.9.0",
|
||||
"js-beautify": "^1.11.0",
|
||||
"querystring": "^0.2.0",
|
||||
|
@ -39,7 +37,6 @@
|
|||
"vue-flatpickr-component": "^8.1.5",
|
||||
"vue-github-button": "^1.1.2",
|
||||
"vue-i18n": "^8.18.1",
|
||||
"vue-moment": "^4.1.0",
|
||||
"vue-observe-visibility": "^0.4.6",
|
||||
"vue-router": "~3.0",
|
||||
"vuedraggable": "^2.23.2",
|
||||
|
|
|
@ -43,19 +43,11 @@
|
|||
<meta property="twitter:image" content="favicon/social.png">
|
||||
|
||||
{{if USE_CDN}}
|
||||
<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}}
|
||||
{{if USING_ASSETS}}
|
||||
<link href="css/vendor.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
<link href="css/main.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://assets.statping.com/css/vendor.css">
|
||||
{{else}}
|
||||
<% _.each(htmlWebpackPlugin.tags.headTags, function(headTag) { %>
|
||||
<%= headTag %> <% }) %>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
@ -65,11 +57,10 @@
|
|||
<div id="app" class="statping_container"></div>
|
||||
|
||||
{{if USE_CDN}}
|
||||
<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>
|
||||
<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/main.chunk.js"></script>
|
||||
{{else}}
|
||||
<% _.each(htmlWebpackPlugin.tags.bodyTags, function(bodyTag) { %>
|
||||
<%= bodyTag %> <% }) %>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 17 KiB |
|
@ -1,12 +1,9 @@
|
|||
import Vue from "vue";
|
||||
import axios from 'axios'
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import * as Integrations from "@sentry/integrations";
|
||||
|
||||
const qs = require('querystring');
|
||||
axios.defaults.withCredentials = true
|
||||
|
||||
const tokenKey = "statping_auth";
|
||||
const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping.com/3"
|
||||
|
||||
class Api {
|
||||
constructor() {
|
||||
|
@ -235,6 +232,11 @@ class Api {
|
|||
return axios.post('api/theme', data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async check_token(token) {
|
||||
const f = {token: token}
|
||||
return axios.post('api/users/token', qs.stringify(f)).then(response => (response.data))
|
||||
}
|
||||
|
||||
async login(username, password) {
|
||||
const f = {username: username, password: password}
|
||||
return axios.post('api/login', qs.stringify(f)).then(response => (response.data))
|
||||
|
@ -272,13 +274,6 @@ class Api {
|
|||
await axios.all([all])
|
||||
}
|
||||
|
||||
async sentry_init() {
|
||||
Sentry.init({
|
||||
dsn: errorReporter,
|
||||
integrations: [new Integrations.Vue({Vue, attachProps: true})],
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
const api = new Api()
|
||||
export default api
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view :loaded="loaded"/>
|
||||
<Footer v-if="$route.path !== '/setup'"/>
|
||||
<Footer v-if="$route.path !== '/setup'"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Footer from "./components/Index/Footer";
|
||||
const Footer = () => import(/* webpackChunkName: "index" */ "./components/Index/Footer");
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
Footer
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loaded: false,
|
||||
name: 'app',
|
||||
components: {
|
||||
Footer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loaded: false,
|
||||
version: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
core() {
|
||||
return this.$store.getters.core
|
||||
}
|
||||
},
|
||||
async beforeMount() {
|
||||
await this.$store.dispatch('loadCore')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
core() {
|
||||
return this.$store.getters.core
|
||||
}
|
||||
},
|
||||
async beforeMount() {
|
||||
await this.$store.dispatch('loadCore')
|
||||
|
||||
this.$i18n.locale = this.core.language || "en";
|
||||
// this.$i18n.locale = "ru";
|
||||
this.$i18n.locale = this.core.language || "en";
|
||||
// this.$i18n.locale = "ru";
|
||||
|
||||
if (!this.core.setup) {
|
||||
this.$router.push('/setup')
|
||||
}
|
||||
if (this.$route.path !== '/setup') {
|
||||
if (this.$store.state.admin) {
|
||||
await this.$store.dispatch('loadAdmin')
|
||||
} else {
|
||||
await this.$store.dispatch('loadRequired')
|
||||
if (this.$route.path !== '/setup') {
|
||||
if (this.$store.state.admin) {
|
||||
await this.$store.dispatch('loadAdmin')
|
||||
} else {
|
||||
await this.$store.dispatch('loadRequired')
|
||||
}
|
||||
this.loaded = true
|
||||
}
|
||||
this.loaded = true
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
if (this.$route.path !== '/setup') {
|
||||
if (this.$store.state.admin) {
|
||||
this.logged_in = true
|
||||
// await this.$store.dispatch('loadAdmin')
|
||||
}
|
||||
}
|
||||
if (this.$route.path !== '/setup') {
|
||||
if (this.$store.state.admin) {
|
||||
this.logged_in = true
|
||||
// await this.$store.dispatch('loadAdmin')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./assets/css/bootstrap.min.css";
|
||||
@import "./assets/scss/main";
|
||||
@import "./assets/scss/index";
|
||||
</style>
|
||||
|
|
|
@ -154,6 +154,10 @@
|
|||
padding: 5px 7px;
|
||||
}
|
||||
|
||||
.service_li {
|
||||
min-height: 115px !important;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
line-height: 1.3;
|
||||
font-size: 0.75rem;
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
.form-control[readonly] {
|
||||
background-color: lighten($background-color, 12%) !important;
|
||||
color: darken($background-color, 5%) !important;
|
||||
color: lighten($input-color, 30%) !important;
|
||||
}
|
||||
|
||||
/* The slider itself */
|
||||
|
@ -268,6 +268,10 @@ input.inputTags-field:focus {
|
|||
color: $text-color;
|
||||
}
|
||||
|
||||
.nav-link.active A:HOVER {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.nav-pills I {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
@import 'base';
|
||||
@import 'variables';
|
||||
@import 'mixin';
|
||||
@import 'layout';
|
||||
@import 'base';
|
||||
@import 'forms';
|
||||
@import 'mobile';
|
|
@ -15,7 +15,7 @@ A:HOVER {
|
|||
}
|
||||
|
||||
.text-muted {
|
||||
color: darken($text-color, 30%) !important;
|
||||
color: lighten($text-color, 30%) !important;
|
||||
}
|
||||
|
||||
.day-success {
|
||||
|
|
|
@ -74,10 +74,19 @@
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
line-height: 1.4rem;
|
||||
font-size: 0.83rem;
|
||||
}
|
||||
.btn-sm {
|
||||
line-height: 1.3;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.switch LABEL {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.full-col-12 {
|
||||
padding-left: 0px;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* Index Page */
|
||||
$background-color: #f5f5f5;
|
||||
$background-color: #EAEAEA;
|
||||
$container-color: #ffffff;
|
||||
$text-color: #1d1d1d;
|
||||
$text-color: #2a2a2a;
|
||||
$max-width: 860px;
|
||||
$title-color: #4e4e4e;
|
||||
$description-color: #828282;
|
||||
|
@ -15,7 +15,7 @@ $navbar-background: #ffffff;
|
|||
$input-background: #fdfdfd;
|
||||
$input-color: #4e4e4e;
|
||||
$input-border: 1px solid #c9c9c9;
|
||||
$day-success-background: #18ce08;
|
||||
$day-success-background: #20ac13;
|
||||
$day-error-background: #d50a0a;
|
||||
|
||||
/* Status Container */
|
||||
|
|
|
@ -6,7 +6,7 @@ CodeMirror.defineMode('mymode', () => {
|
|||
if (stream.match(".Service") || (stream.match(".Core")) || (stream.match(".Failure"))) {
|
||||
return "var-highlight"
|
||||
} else if (stream.match(".Id") || stream.match(".Domain") || stream.match(".CreatedAt") ||
|
||||
stream.match(".Name") || stream.match(".DowntimeAgo") || stream.match(".Issue") || stream.match(".LastStatusCode") ||
|
||||
stream.match(".Name") || stream.match(".Downtime.Human") || stream.match(".Issue") || stream.match(".LastStatusCode") ||
|
||||
stream.match(".Port") || stream.match(".FailuresLast24Hours") || stream.match(".PingTime")) {
|
||||
return "var-sub-highlight"
|
||||
} else if (stream.match("{{") || stream.match("}}")) {
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
<div v-for="(checkin, i) in checkins" class="card text-black-50 bg-white mt-3">
|
||||
<div class="card-header text-capitalize">
|
||||
{{checkin.name}}
|
||||
<button @click="deleteCheckin(checkin)" class="btn btn-sm btn-danger float-right text-uppercase">Delete</button>
|
||||
<button @click="deleteCheckin(checkin)" class="btn btn-sm small btn-danger float-right text-uppercase">Delete</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" :value="`${core.domain}/checkin/${checkin.api_key}`" readonly>
|
||||
<div class="input-group-append copy-btn">
|
||||
|
@ -18,8 +17,8 @@
|
|||
</div>
|
||||
|
||||
<span class="small">Send a GET request to this URL every {{checkin.interval}} minutes</span>
|
||||
<span class="small float-right mt-1">Requested {{ago(checkin.last_hit)}} ago</span>
|
||||
<span class="small float-right mt-1 mr-3">Request expected every {{checkin.interval}} minutes</span>
|
||||
<span class="small float-right mt-1 mr-3 d-none d-md-block">Requested {{ago(checkin.last_hit)}} ago</span>
|
||||
<span class="small float-right mt-1 mr-3 d-none d-md-block">Request expected every {{checkin.interval}} minutes</span>
|
||||
|
||||
<div class="card text-black-50 bg-white mt-3">
|
||||
<div class="card-header text-capitalize">
|
||||
|
@ -62,15 +61,15 @@
|
|||
<div class="card-body">
|
||||
<form @submit.prevent="saveCheckin">
|
||||
<div class="form-group row">
|
||||
<div class="col-5">
|
||||
<div class="col-7 col-md-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-3">
|
||||
<div class="col-5 col-md-3">
|
||||
<label for="checkin_interval" class="col-form-label">Interval (minutes)</label>
|
||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="1" min="1">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<label class="col-form-label"></label>
|
||||
<button :disabled="btn_disabled" @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-primary d-block mt-2">Save Checkin</button>
|
||||
</div>
|
||||
|
@ -137,6 +136,9 @@ export default {
|
|||
},
|
||||
last_record(checkin) {
|
||||
const r = this.records(checkin)
|
||||
if (r.length === 0) {
|
||||
return {success: false}
|
||||
}
|
||||
return r[0]
|
||||
},
|
||||
fixInts() {
|
||||
|
@ -161,10 +163,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
const ServiceInfo = () => import('@/components/Dashboard/ServiceInfo')
|
||||
const ServiceInfo = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServiceInfo')
|
||||
|
||||
export default {
|
||||
name: 'DashboardIndex',
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<script>
|
||||
import Api from "../../API"
|
||||
import FormMessage from "../../forms/Message";
|
||||
const FormMessage = () => import(/* webpackChunkName: "dashboard" */ "../../forms/Message");
|
||||
|
||||
export default {
|
||||
name: 'DashboardMessages',
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('dashboard.name') }}</th>
|
||||
<th scope="col">{{ $tc('dashboard.service', 2) }}</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">{{ $tc('dashboard.service', 2) }}</th>
|
||||
<th scope="col">{{ $t('dashboard.visibility') }}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<td><span class="drag_icon d-none d-md-inline">
|
||||
<font-awesome-icon icon="bars" class="mr-3" /></span> {{group.name}}
|
||||
</td>
|
||||
<td>{{$store.getters.servicesInGroup(group.id).length}}</td>
|
||||
<td class="d-none d-md-table-cell">{{$store.getters.servicesInGroup(group.id).length}}</td>
|
||||
<td>
|
||||
<span class="badge text-uppercase" :class="{'badge-primary': group.public, 'badge-secondary': !group.public}">
|
||||
{{group.public ? $t('public') : $t('private')}}
|
||||
|
@ -67,11 +67,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
const FormGroup = () => import('@/forms/Group')
|
||||
const ToggleSwitch = () => import('@/forms/ToggleSwitch')
|
||||
const ServicesList = () => import('@/components/Dashboard/ServicesList')
|
||||
const FormGroup = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Group')
|
||||
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '@/forms/ToggleSwitch')
|
||||
const ServicesList = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServicesList')
|
||||
import Api from "../../API";
|
||||
import draggable from 'vuedraggable'
|
||||
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
||||
|
||||
export default {
|
||||
name: 'DashboardServices',
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
<script>
|
||||
import Api from "../../API"
|
||||
const FormUser = () => import('@/forms/User')
|
||||
const FormUser = () => import(/* webpackChunkName: "dashboard" */ '@/forms/User')
|
||||
|
||||
export default {
|
||||
name: 'DashboardUsers',
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import FormGroup from "../../forms/Group";
|
||||
const FormGroup = () => import(/* webpackChunkName: "dashboard" */ "../../forms/Group");
|
||||
import Api from "../../API";
|
||||
import ToggleSwitch from "../../forms/ToggleSwitch";
|
||||
import draggable from 'vuedraggable'
|
||||
import FormService from "../../forms/Service";
|
||||
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ "../../forms/ToggleSwitch");
|
||||
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
||||
const FormService = () => import(/* webpackChunkName: "dashboard" */ "../../forms/Service");
|
||||
|
||||
export default {
|
||||
name: 'EditService',
|
||||
|
|
|
@ -1,22 +1,77 @@
|
|||
<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 v-if="service" class="col-12">
|
||||
<h3>{{service.name}} Failures
|
||||
<button v-if="failures.length>0" @click="deleteFailures" class="btn btn-danger float-right">Delete All</button>
|
||||
</h3>
|
||||
|
||||
<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 class="card mt-4 mb-4">
|
||||
<div class="card-header">
|
||||
Search and Filter
|
||||
<span class="float-right">
|
||||
<font-awesome-icon v-if="loading" icon="circle-notch" spin/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<label for="fromdate">From Date</label>
|
||||
<flatPickr id="fromdate" :disabled="loading" @on-change="load" v-model="start_time" :config="{ wrap: true, allowInput: true, enableTime: true, dateFormat: 'Z', altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="form-control text-left d-block" required />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="todate">To Date</label>
|
||||
<flatPickr id="todate" :disabled="loading" @on-change="load" v-model="end_time" :config="{ wrap: true, allowInput: true, enableTime: true, dateFormat: 'Z', altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="form-control text-left d-block" required />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="search">Search Terms</label>
|
||||
<input id="search" type="text" v-model="search" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row mt-3">
|
||||
<div class="col">
|
||||
<span @click="show_checkins = !!show_checkins" class="switch float-left">
|
||||
<input v-model="show_checkins" type="checkbox" class="switch" id="showcheckins" v-bind:checked="show_checkins">
|
||||
<label v-if="show_checkins" for="showcheckins">Showing Checkin Failures</label>
|
||||
<label v-else for="showcheckins">View Checkin Failures</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p class="mb-1">{{failure.issue}}</p>
|
||||
</div>
|
||||
|
||||
<nav v-if="total > 4" class="mt-3">
|
||||
<div v-if="failures.length === 0" class="alert alert-info">
|
||||
<span v-if="search">
|
||||
Could not find any failures with issue: "{{search}}"
|
||||
</span>
|
||||
<span v-else>
|
||||
You don't have any failures for {{service.name}}. Way to go!
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table v-else class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Issue</th>
|
||||
<th scope="col">Status Code</th>
|
||||
<th scope="col">Ping</th>
|
||||
<th scope="col">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(failure, index) in failures" :key="index">
|
||||
<th class="font-1" scope="row">{{failure.id}}</th>
|
||||
<td class="font-1">{{failure.issue}}</td>
|
||||
<td class="font-1">{{failure.error_code}}</td>
|
||||
<td class="font-1">{{humanTime(failure.ping)}}</td>
|
||||
<td class="font-1">{{ago(failure.created_at)}}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<nav v-if="total > 4 && failures.length !== 0" 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">
|
||||
|
@ -35,42 +90,59 @@
|
|||
</li>
|
||||
</ul>
|
||||
<div class="text-center">
|
||||
<span>{{total}} Failures</span>
|
||||
<span>{{total}} Failures</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
import 'flatpickr/dist/flatpickr.css';
|
||||
|
||||
export default {
|
||||
name: 'Failures',
|
||||
components: {
|
||||
flatPickr
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
service: {},
|
||||
loading: true,
|
||||
search: "",
|
||||
show_checkins: false,
|
||||
service: null,
|
||||
fails: [],
|
||||
limit: 10,
|
||||
limit: 64,
|
||||
offset: 0,
|
||||
total: 0,
|
||||
page: 1
|
||||
page: 1,
|
||||
start_time: this.nowSubtract(216000).toISOString(),
|
||||
end_time: this.nowSubtract(0).toISOString(),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route': 'reloadTimes',
|
||||
},
|
||||
computed: {
|
||||
failures() {
|
||||
return this.fails.sort(function(a,b) {return b.id - a.id;});
|
||||
let sorted = this.fails
|
||||
if (this.show_checkins) {
|
||||
sorted = sorted.filter(f => f.method === "checkin");
|
||||
} else {
|
||||
sorted = sorted.filter(f => f.method !== "checkin");
|
||||
}
|
||||
if (this.search !== "") {
|
||||
sorted = sorted.filter(f => f.issue.toLowerCase().includes(this.search));
|
||||
}
|
||||
return sorted
|
||||
},
|
||||
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
|
||||
}
|
||||
return Math.floor(this.total / this.limit)
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
|
@ -94,15 +166,10 @@ export default {
|
|||
await this.load()
|
||||
},
|
||||
async load() {
|
||||
this.fails = await Api.service_failures(this.service.id, 0, 9999999999, this.limit, this.offset)
|
||||
this.loading = true
|
||||
this.fails = await Api.service_failures(this.service.id, this.toUnix(this.parseISO(this.start_time)), this.toUnix(this.parseISO(this.end_time)), this.limit, this.offset)
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div v-for="incident in incidents" :key="incident.id" class="card contain-card 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
|
||||
<font-awesome-icon icon="times" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -50,8 +50,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
const FormIncidentUpdates = () => import('@/forms/IncidentUpdates')
|
||||
import Api from "../../API";
|
||||
|
||||
const FormIncidentUpdates = () => import(/* webpackChunkName: "dashboard" */ '@/forms/IncidentUpdates')
|
||||
|
||||
export default {
|
||||
name: 'Incidents',
|
||||
|
@ -112,10 +113,3 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div>
|
||||
<Loading :loading="!loaded"/>
|
||||
<div v-if="loaded && !service.online" class="bg-white shadow-sm mt-3 p-3 pr-4 pl-4 col-12">
|
||||
<font-awesome-icon icon="exclamation" class="mr-3" size="1x"/>
|
||||
Last failure was {{ago(service.last_error)}} ago.
|
||||
<code v-if="failure" class="d-block bg-light p-3 mt-3">
|
||||
{{failure.issue}}
|
||||
<span class="d-block text-dim float-right small mt-3 mb-1">Failure #{{failure.id}}</span>
|
||||
</code>
|
||||
</div>
|
||||
<div v-if="loaded" v-for="message in messages" class="bg-light shadow-sm p-3 pr-4 pl-4 col-12 mt-3">
|
||||
<font-awesome-icon icon="calendar" class="mr-3" size="1x"/> {{message.description}}
|
||||
<span class="d-block small text-muted mt-3">
|
||||
Starts at <strong>{{niceDate(message.start_on)}}</strong> till <strong>{{niceDate(message.end_on)}}</strong>
|
||||
({{dur(parseISO(message.start_on), parseISO(message.end_on))}})
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="loaded" v-for="incident in incidents" class="bg-light shadow-sm p-3 pr-4 pl-4 col-12 mt-3">
|
||||
<font-awesome-icon icon="calendar" class="mr-3" size="1x"/>
|
||||
{{incident.title}} - {{incident.description}}
|
||||
<div v-for="update in incident.updates" class="d-block small">
|
||||
<span class="font-weight-bold text-capitalize">{{update.type}}</span> - {{update.message}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
const Loading = () => import(/* webpackChunkName: "index" */ "@/components/Elements/Loading");
|
||||
|
||||
export default {
|
||||
name: "ServiceEvents",
|
||||
components: {
|
||||
Loading
|
||||
|
||||
},
|
||||
props: {
|
||||
service: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: null,
|
||||
incidents: null,
|
||||
failure: null,
|
||||
loaded: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
async load() {
|
||||
this.loaded = false
|
||||
if (!this.service.online) {
|
||||
await this.getFailure()
|
||||
}
|
||||
await this.getMessages()
|
||||
await this.getIncidents()
|
||||
this.loaded = true
|
||||
},
|
||||
async getMessages() {
|
||||
this.messages = await Api.messages()
|
||||
},
|
||||
async getFailure() {
|
||||
const f = await Api.service_failures(this.service.id, null, null, 1)
|
||||
this.failure = f[0]
|
||||
},
|
||||
async getIncidents() {
|
||||
this.incidents = await Api.incidents_service(this.service.id)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="card mb-4" :class="{'offline-card': !service.online}">
|
||||
<div class="card-title px-4 pt-3">
|
||||
<div class="card-header pb-1">
|
||||
<h4 v-observe-visibility="setVisible">
|
||||
<router-link :to="serviceLink(service)">{{service.name}}</router-link>
|
||||
<span class="badge float-right text-uppercase" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
||||
|
@ -9,61 +9,31 @@
|
|||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-3 p-md-1 pt-md-1 pb-md-1">
|
||||
|
||||
<div class="card-body">
|
||||
<transition name="fade">
|
||||
<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 css="text-danger font-5 font-weight-bold"></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 v-if="loaded" class="row pl-2 pr-2">
|
||||
<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"/>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 mt-4 mt-md-0 mb-3">
|
||||
<ServiceSparkLine :title="set1_name" subtitle="Latency Last 7 Days" :series="set1"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-2 mt-md-0 mb-3">
|
||||
<ServiceEvents :service="service"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else class="row mt-5 mb-5 pt-5 pb-5">
|
||||
<div class="col-6 text-center text-muted">
|
||||
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
||||
</div>
|
||||
<div class="col-6 text-center text-muted">
|
||||
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="row">
|
||||
|
||||
|
@ -100,19 +70,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Checkin from '../../forms/Checkin';
|
||||
import FormIncident from '../../forms/Incident';
|
||||
import FormMessage from '../../forms/Message';
|
||||
import ServiceFailures from '../Service/ServiceFailures';
|
||||
import ServiceSparkLine from "./ServiceSparkLine";
|
||||
const Checkin = () => import(/* webpackChunkName: "dashboard" */ '../../forms/Checkin');
|
||||
const FormMessage = () => import(/* webpackChunkName: "dashboard" */ '../../forms/Message');
|
||||
const ServiceFailures = () => import(/* webpackChunkName: "dashboard" */ '../Service/ServiceFailures');
|
||||
const ServiceSparkLine = () => import(/* webpackChunkName: "dashboard" */ "./ServiceSparkLine");
|
||||
import Api from "../../API";
|
||||
|
||||
const ServiceEvents = () => import(/* webpackChunkName: "dashboard" */ "@/components/Dashboard/ServiceEvents");
|
||||
|
||||
export default {
|
||||
name: 'ServiceInfo',
|
||||
components: {
|
||||
ServiceEvents,
|
||||
Checkin,
|
||||
ServiceFailures,
|
||||
FormIncident,
|
||||
FormMessage,
|
||||
ServiceSparkLine
|
||||
},
|
||||
|
@ -197,16 +168,3 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.offline-card {
|
||||
background-color: #fff5f5;
|
||||
}
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .75s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<script>
|
||||
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ServiceSparkLine',
|
||||
props: {
|
||||
|
|
|
@ -33,13 +33,13 @@
|
|||
</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<button v-if="$store.state.admin" @click.prevent="goto({path: `/dashboard/edit_service/${service.id}`, params: {service: service} })" class="btn btn-sm btn-outline-secondary">
|
||||
<button :disabled="loading" v-if="$store.state.admin" @click.prevent="goto({path: `/dashboard/edit_service/${service.id}`, params: {service: service} })" class="btn btn-sm btn-outline-secondary">
|
||||
<font-awesome-icon icon="edit" />
|
||||
</button>
|
||||
<button @click.prevent="goto({path: serviceLink(service), params: {service: service} })" class="btn btn-sm btn-outline-secondary">
|
||||
<button :disabled="loading" @click.prevent="goto({path: serviceLink(service), params: {service: service} })" class="btn btn-sm btn-outline-secondary">
|
||||
<font-awesome-icon icon="chart-area" />
|
||||
</button>
|
||||
<button v-if="$store.state.admin" @click.prevent="deleteService(service)" href="#" class="btn btn-sm btn-danger">
|
||||
<button :disabled="loading" v-if="$store.state.admin" @click.prevent="deleteService(service)" class="btn btn-sm btn-danger">
|
||||
<font-awesome-icon v-if="!loading" icon="times" />
|
||||
<font-awesome-icon v-if="loading" icon="circle-notch" spin/>
|
||||
</button>
|
||||
|
@ -53,8 +53,8 @@
|
|||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
import draggable from 'vuedraggable'
|
||||
import ToggleSwitch from '../../forms/ToggleSwitch';
|
||||
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
||||
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '../../forms/ToggleSwitch');
|
||||
|
||||
export default {
|
||||
name: 'ServicesList',
|
||||
|
|
|
@ -57,15 +57,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../../API";
|
||||
import Api from "../../API";
|
||||
|
||||
// require component
|
||||
import { codemirror } from 'vue-codemirror'
|
||||
import 'codemirror/mode/css/css.js'
|
||||
// require component
|
||||
import {codemirror} from 'vue-codemirror'
|
||||
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror-colorpicker/dist/codemirror-colorpicker.css'
|
||||
import 'codemirror-colorpicker'
|
||||
import('codemirror/mode/css/css.js')
|
||||
|
||||
import('codemirror/lib/codemirror.css')
|
||||
import('codemirror-colorpicker/dist/codemirror-colorpicker.css')
|
||||
import('codemirror-colorpicker')
|
||||
|
||||
export default {
|
||||
name: 'ThemeEditor',
|
||||
|
@ -191,9 +192,3 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.CodeMirror {
|
||||
border: 1px solid #eee;
|
||||
height: 550px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
|
||||
<script>
|
||||
import Api from "../../API"
|
||||
import Vue from "vue";
|
||||
|
||||
export default {
|
||||
name: 'TopNav',
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="col-12 mb-3 pb-2 border-bottom" role="alert">
|
||||
<span class="font-weight-bold text-capitalize" :class="{'text-success': update.type.toLowerCase()==='resolved', 'text-danger': update.type.toLowerCase()==='investigating', 'text-warning': update.type.toLowerCase()==='update'}">{{update.type}}</span>
|
||||
<span class="text-muted">- {{update.message}}
|
||||
<button v-if="admin" @click="delete_update(update)" type="button" class="close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</span>
|
||||
<span class="d-block small">{{ago(update.created_at)}} ago</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "@/API";
|
||||
|
||||
export default {
|
||||
name: "IncidentUpdate",
|
||||
props: {
|
||||
update: {
|
||||
required: true
|
||||
},
|
||||
admin: {
|
||||
required: true
|
||||
},
|
||||
onUpdate: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async delete_update(update) {
|
||||
this.res = await Api.incident_update_delete(update)
|
||||
if (this.res.status === "success") {
|
||||
this.onUpdate()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div v-if="loading" class="row mt-3 mb-3">
|
||||
<div class="col-12 text-center text-muted">
|
||||
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Loading",
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -3,7 +3,7 @@
|
|||
<h4 v-if="group.name !== 'Empty Group'" class="group_header mb-3 mt-4">{{group.name}}</h4>
|
||||
<div class="list-group online_list mb-4">
|
||||
|
||||
<a v-for="(service, index) in $store.getters.servicesInGroup(group.id)" v-bind:key="index" class="service_li list-group-item list-group-item-action">
|
||||
<div v-for="(service, index) in $store.getters.servicesInGroup(group.id)" v-bind:key="index" class="service_li list-group-item list-group-item-action">
|
||||
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
|
||||
<span class="badge text-uppercase float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">
|
||||
{{service.online ? $t('online') : $t('offline')}}
|
||||
|
@ -12,16 +12,16 @@
|
|||
<GroupServiceFailures :service="service"/>
|
||||
|
||||
<IncidentsBlock :service="service"/>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
import GroupServiceFailures from './GroupServiceFailures';
|
||||
import IncidentsBlock from './IncidentsBlock';
|
||||
const GroupServiceFailures = () => import(/* webpackChunkName: "index" */ './GroupServiceFailures');
|
||||
const IncidentsBlock = () => import(/* webpackChunkName: "index" */ './IncidentsBlock');
|
||||
|
||||
export default {
|
||||
name: 'Group',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<div class="d-flex mt-3 mb-2">
|
||||
<div class="flex-fill service_day" v-for="(d, index) in failureData" :class="{'day-error': d.amount > 0, 'day-success': d.amount === 0}">
|
||||
<span v-if="d.amount != 0" class="small">{{d.amount}}</span>
|
||||
<span v-if="d.amount !== 0" class="d-none d-md-block text-center small">{{d.amount}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div v-for="(incident, i) in incidents" class="col-12 mt-4 mb-3">
|
||||
<div v-for="(incident, i) in incidents" class="col-12">
|
||||
<span class="braker mt-1 mb-3"></span>
|
||||
<h6>Incident: {{incident.title}}
|
||||
<h6>{{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 class="font-2 mb-3" v-html="incident.description"></div>
|
||||
<IncidentUpdate v-for="(update, i) in incident.updates" v-bind:key="i" :update="update" :admin="false"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
import UpdatesBlock from "@/components/Index/UpdatesBlock";
|
||||
import IncidentUpdate from "@/components/Elements/IncidentUpdate";
|
||||
|
||||
export default {
|
||||
name: 'IncidentsBlock',
|
||||
components: {UpdatesBlock},
|
||||
components: {
|
||||
IncidentUpdate
|
||||
},
|
||||
props: {
|
||||
service: {
|
||||
type: Object,
|
||||
|
@ -49,8 +49,7 @@ export default {
|
|||
this.incidents = await Api.incidents_service(this.service.id)
|
||||
},
|
||||
async incident_updates(incident) {
|
||||
await Api.incident_updates(incident).then((d) => {return d})
|
||||
return o
|
||||
return await Api.incident_updates(incident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="alert alert-warning pb-4 pt-3 mt-5 mb-5" role="alert">
|
||||
<div class="alert alert-secondary pb-3 pt-3 mt-5 mb-3" role="alert">
|
||||
<h3 class="mb-3">{{message.title}}</h3>
|
||||
<span class="mb-3">{{message.description}}</span>
|
||||
<div class="row d-block mt-3">
|
||||
<span class="col-12 col-md-6 text-left small">
|
||||
<span class="col-12 col-md-6 text-left small text-muted">
|
||||
Started {{niceDate(message.start_on)}} ({{ago(message.start_on)}} ago)
|
||||
</span>
|
||||
<span class="col-12 col-md-6 text-right float-right small">
|
||||
<span class="col-12 col-md-6 text-right float-right small text-muted">
|
||||
Ends on {{niceDate(message.end_on)}} (in {{ago(message.end_on)}})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,8 @@ export default {
|
|||
name: 'MessageBlock',
|
||||
props: {
|
||||
message: {
|
||||
type: Object
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
<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 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>
|
|
@ -1,11 +1,6 @@
|
|||
<template>
|
||||
<div class="card text-black-50 bg-white mt-3 mb-3">
|
||||
<div class="card-header text-capitalize">Service Latency</div>
|
||||
<div class="card-body">
|
||||
<div class="service-chart-container">
|
||||
<apexchart width="100%" height="420" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-chart-container">
|
||||
<apexchart width="100%" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<template>
|
||||
<div class="row stats_area mt-5 mb-4">
|
||||
okok
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Incidents',
|
||||
props: {
|
||||
service: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -58,11 +58,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '../../API';
|
||||
import Analytics from './Analytics';
|
||||
import ServiceChart from "./ServiceChart";
|
||||
import ServiceTopStats from "@/components/Service/ServiceTopStats";
|
||||
import Graphing from '../../graphing'
|
||||
const Analytics = () => import(/* webpackChunkName: "service" */ './Analytics');
|
||||
const ServiceChart = () => import(/* webpackChunkName: "service" */ "./ServiceChart");
|
||||
const ServiceTopStats = () => import(/* webpackChunkName: "service" */ "@/components/Service/ServiceTopStats");
|
||||
const Graphing = () => import(/* webpackChunkName: "service" */ '../../graphing');
|
||||
|
||||
export default {
|
||||
name: 'ServiceBlock',
|
||||
|
@ -227,8 +226,4 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
||||
</script>
|
|
@ -178,6 +178,7 @@
|
|||
},
|
||||
methods: {
|
||||
async chartHits(val) {
|
||||
this.ready = false
|
||||
const start = val.start_time
|
||||
const end = this.toUnix(new Date())
|
||||
this.data = await Api.service_hits(this.service.id, start, end, val.interval, false)
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ServiceChart from "./ServiceChart";
|
||||
const ServiceChart = () => import(/* webpackChunkName: "service" */ "./ServiceChart");
|
||||
import Api from "../../API";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -16,19 +16,15 @@
|
|||
<div class="col-12 alert alert-light">
|
||||
<form @submit.prevent="saveCheckin">
|
||||
<div class="form-group row">
|
||||
<div class="col-5">
|
||||
<div class="col-12 col-md-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">
|
||||
<div class="col-12 col-md-5">
|
||||
<label for="checkin_interval" class="col-form-label">Interval (minutes)</label>
|
||||
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="1" min="1">
|
||||
</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">
|
||||
<div class="col-12 col-md-5">
|
||||
<label class="col-form-label"></label>
|
||||
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-success d-block mt-2">Save Checkin</button>
|
||||
</div>
|
||||
|
@ -54,7 +50,6 @@
|
|||
checkin: {
|
||||
name: "",
|
||||
interval: 60,
|
||||
grace: 60,
|
||||
service_id: this.service.id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../API";
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
import 'flatpickr/dist/flatpickr.css';
|
||||
import FormIncidentUpdates from './IncidentUpdates';
|
||||
|
||||
export default {
|
||||
name: 'FormIncident',
|
||||
components: {
|
||||
FormIncidentUpdates
|
||||
},
|
||||
props: {
|
||||
service: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
incident: {
|
||||
title: "",
|
||||
description: "",
|
||||
service: this.service.id,
|
||||
},
|
||||
incidents: [],
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
await this.loadIncidents()
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -5,20 +5,12 @@
|
|||
No updates found, create a new Incident Update below.
|
||||
</div>
|
||||
|
||||
<div v-for="update in updates" :key="update.id">
|
||||
<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 v-for="update in updates.reverse()" :key="update.id">
|
||||
<IncidentUpdate :update="update" :onUpdate="loadUpdates" :admin="true"/>
|
||||
</div>
|
||||
|
||||
<form class="row" @submit.prevent="createIncidentUpdate">
|
||||
<div class="col-3">
|
||||
<div class="col-12 col-md-3 mb-3 mb-md-0">
|
||||
<select v-model="incident_update.type" class="form-control">
|
||||
<option value="Investigating">Investigating</option>
|
||||
<option value="Update">Update</option>
|
||||
|
@ -26,11 +18,11 @@
|
|||
<option value="Resolved">Resolved</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<input v-model="incident_update.message" rows="5" name="description" class="form-control" id="message" required>
|
||||
<div class="col-12 col-md-7 mb-3 mb-md-0">
|
||||
<input v-model="incident_update.message" name="description" class="form-control" id="message" required>
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<div class="col-12 col-md-2">
|
||||
<button @click.prevent="createIncidentUpdate"
|
||||
:disabled="!incident_update.message"
|
||||
type="submit" class="btn btn-block btn-primary">
|
||||
|
@ -44,12 +36,11 @@
|
|||
|
||||
<script>
|
||||
import Api from "../API";
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
import 'flatpickr/dist/flatpickr.css';
|
||||
const IncidentUpdate = () => import(/* webpackChunkName: "index" */ "@/components/Elements/IncidentUpdate");
|
||||
|
||||
export default {
|
||||
name: 'FormIncidentUpdates',
|
||||
components: {},
|
||||
components: {IncidentUpdate},
|
||||
props: {
|
||||
incident: {
|
||||
type: Object,
|
||||
|
@ -58,7 +49,7 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
updates: [],
|
||||
updates: null,
|
||||
incident_update: {
|
||||
incident: this.incident.id,
|
||||
message: "",
|
||||
|
@ -72,15 +63,6 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async delete_update(update) {
|
||||
this.res = await Api.incident_update_delete(update)
|
||||
if (this.res.status === "success") {
|
||||
this.updates = this.updates.filter(obj => obj.id !== update.id); // this is better in terms of not having to querry the db to get a fresh copy of all updates
|
||||
//await this.loadUpdates()
|
||||
}
|
||||
},
|
||||
|
||||
async createIncidentUpdate() {
|
||||
this.res = await Api.incident_update_create(this.incident_update)
|
||||
if (this.res.status === "success") {
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-2 col-form-label">{{$t('username')}}</label>
|
||||
<div class="col-sm-10">
|
||||
<input @keyup="checkForm" type="text" v-model="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
|
||||
<input @keyup="checkForm" type="text" v-model="username" autocomplete="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">{{$t('password')}}</label>
|
||||
<div class="col-sm-10">
|
||||
<input @keyup="checkForm" type="password" v-model="password" name="password" class="form-control" id="password" placeholder="Password">
|
||||
<input @keyup="checkForm" type="password" v-model="password" autocomplete="current-password" name="password" class="form-control" id="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -59,17 +59,20 @@
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
username: "",
|
||||
password: "",
|
||||
auth: {},
|
||||
loading: false,
|
||||
error: false,
|
||||
disabled: true,
|
||||
username: "",
|
||||
password: "",
|
||||
auth: {},
|
||||
loading: false,
|
||||
error: false,
|
||||
disabled: true,
|
||||
google_scope: "https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email",
|
||||
slack_scope: "identity.email,identity.basic"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mounted() {
|
||||
this.$cookies.remove("statping_auth")
|
||||
},
|
||||
methods: {
|
||||
checkForm() {
|
||||
if (!this.username || !this.password) {
|
||||
this.disabled = true
|
||||
|
@ -84,9 +87,10 @@
|
|||
if (auth.error) {
|
||||
this.error = true
|
||||
} else if (auth.token) {
|
||||
// this.$cookies.set("statping_auth", auth.token)
|
||||
this.$cookies.set("statping_auth", auth.token)
|
||||
await this.$store.dispatch('loadAdmin')
|
||||
this.$store.commit('setAdmin', auth.admin)
|
||||
this.$store.commit('setLoggedIn', true)
|
||||
this.$router.push('/dashboard')
|
||||
}
|
||||
this.loading = false
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<label class="col-sm-4 col-form-label">Service</label>
|
||||
<div class="col-sm-8">
|
||||
<select v-model="message.service" name="service_id" class="form-control">
|
||||
<option :value="0">Global Announcement</option>
|
||||
<option v-bind:value="0">Global Announcement</option>
|
||||
<option v-for="service in $store.getters.services" :value="service.id" v-bind:key="service.id" >{{service.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -39,7 +39,7 @@
|
|||
<div class="col-sm-4">
|
||||
<flatPickr v-model="message.start_on" @on-change="startChange" :config="config" type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="0001-01-01T00:00:00Z" required />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="col-sm-4 mt-3 mt-md-0">
|
||||
<flatPickr v-model="message.end_on" @on-change="endChange" :config="config" type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="0001-01-01T00:00:00Z" required />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,7 +48,7 @@
|
|||
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
|
||||
<div class="col-sm-8">
|
||||
<select v-model="message.service" class="form-control" name="service" id="service_id">
|
||||
<option :value="0">Global Message</option>
|
||||
<option v-bind:value="0">Global Message</option>
|
||||
<option v-for="service in $store.getters.services" :value="service.id" v-bind:key="service.id">{{service.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -130,6 +130,7 @@
|
|||
start_on: new Date(),
|
||||
end_on: new Date(),
|
||||
service_id: 0,
|
||||
service: 0,
|
||||
notify_method: "",
|
||||
notify: false,
|
||||
notify_before: 0,
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
<div v-if="notifier.method==='mobile'">
|
||||
<div class="form-group row mt-3">
|
||||
<label for="domain" class="col-sm-4 col-form-label">Statping Domain</label>
|
||||
<label for="statping_domain" class="col-sm-4 col-form-label">Statping Domain</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<input v-bind:value="$store.getters.core.domain" type="text" class="form-control" id="domain" readonly>
|
||||
<input v-bind:value="$store.getters.core.domain" type="text" class="form-control" id="statping_domain" readonly>
|
||||
<div class="input-group-append copy-btn">
|
||||
<button @click.prevent="copy($store.getters.core.domain)" class="btn btn-outline-secondary" type="button">Copy</button>
|
||||
</div>
|
||||
|
@ -69,7 +69,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="notifier.data_type" class="card mb-3">
|
||||
<div v-if="notifier.data_type" class="card mb-3">
|
||||
<div class="card-header text-capitalize">
|
||||
<font-awesome-icon @click="expanded = !expanded" :icon="expanded ? 'minus' : 'plus'" class="mr-2 pointer"/>
|
||||
{{notifier.title}} Outgoing Request
|
||||
|
@ -136,6 +136,31 @@
|
|||
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="notifier.logs" class="card mb-3">
|
||||
<div class="card-header text-capitalize">
|
||||
<font-awesome-icon @click="expanded_logs = !expanded_logs" :icon="expanded_logs ? 'minus' : 'plus'" class="mr-2 pointer"/>
|
||||
{{notifier.title}} Logs
|
||||
<span class="badge badge-info float-right text-uppercase mt-1">{{notifier.logs.length}}</span>
|
||||
</div>
|
||||
<div class="card-body" :class="{'d-none': !expanded_logs}">
|
||||
|
||||
<div v-for="(log, i) in notifier.logs.reverse()" class="alert" :class="{'alert-danger': log.error, 'alert-dark': !log.success && !log.error, 'alert-success': log.success && !log.error}">
|
||||
<span class="d-block">
|
||||
Service '{{$store.getters.serviceById(log.service).name}}'
|
||||
{{log.success ? "Success Triggered" : "Failure Triggered"}}
|
||||
</span>
|
||||
|
||||
<div class="bg-white p-3 small mt-2">
|
||||
<code>{{log.message}}</code>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<span class="col-6 small">{{niceDate(log.created_at)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -148,16 +173,19 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../API";
|
||||
import Api from "../API";
|
||||
/* webpackChunkName: "codemirror" */
|
||||
import {codemirror} from 'vue-codemirror'
|
||||
/* webpackChunkName: "codemirror" */
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
/* webpackChunkName: "codemirror" */
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
/* webpackChunkName: "codemirror" */
|
||||
import 'codemirror/theme/neat.css'
|
||||
/* webpackChunkName: "codemirror" */
|
||||
import '../codemirror_json'
|
||||
|
||||
const beautify = require('js-beautify').js
|
||||
|
||||
// require component
|
||||
import { codemirror } from 'vue-codemirror'
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror/theme/neat.css'
|
||||
import '../codemirror_json'
|
||||
const beautify = require('js-beautify').js
|
||||
|
||||
export default {
|
||||
name: 'Notifier',
|
||||
|
@ -183,6 +211,7 @@ export default {
|
|||
success: false,
|
||||
saved: false,
|
||||
expanded: false,
|
||||
expanded_logs: false,
|
||||
success_data: null,
|
||||
failure_data: null,
|
||||
form: {},
|
||||
|
@ -300,10 +329,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.CodeMirror {
|
||||
border: 1px solid #eee;
|
||||
height: 550px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -50,12 +50,6 @@
|
|||
<small>Optional comma delimited list of Github Organizations</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Enable Github Login</label>
|
||||
<div class="col-md-8 col-xs-12 mt-1">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
|
||||
<div class="col-sm-8">
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Public Service</label>
|
||||
<div class="col-8 mt-1">
|
||||
<div class="col-12 col-md-8 mt-1 mb-2">
|
||||
<span @click="service.public = !!service.public" class="switch float-left">
|
||||
<input v-model="service.public" type="checkbox" name="public-option" class="switch" id="switch-public" v-bind:checked="service.public">
|
||||
<label v-if="service.public" for="switch-public">This service will be visible for everyone</label>
|
||||
|
@ -145,8 +145,8 @@
|
|||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Follow HTTP Redirects</label>
|
||||
<div class="col-8 mt-1">
|
||||
<label class="col-12 col-md-4 col-form-label">Follow HTTP Redirects</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
<span @click="service.redirect = !!service.redirect" class="switch float-left">
|
||||
<input v-model="service.redirect" type="checkbox" name="redirect-option" class="switch" id="switch-redirect" v-bind:checked="service.redirect">
|
||||
<label for="switch-redirect">Follow HTTP Redirects if server attempts</label>
|
||||
|
@ -155,8 +155,8 @@
|
|||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Verify SSL</label>
|
||||
<div class="col-8 mt-1">
|
||||
<label class="col-12 col-md-4 col-form-label">Verify SSL</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
<span @click="service.verify_ssl = !!service.verify_ssl" class="switch float-left">
|
||||
<input v-model="service.verify_ssl" type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" v-bind:checked="service.verify_ssl">
|
||||
<label for="switch-verify-ssl" v-if="service.verify_ssl">Verify SSL Certificate for this service</label>
|
||||
|
@ -166,8 +166,8 @@
|
|||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(tcp|http)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Use TLS Certificate</label>
|
||||
<div class="col-8 mt-1">
|
||||
<label class="col-12 col-md-4 col-form-label">Use TLS Certificate</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
<span @click="use_tls = !!use_tls" class="switch float-left">
|
||||
<input v-model="use_tls" type="checkbox" name="verify_ssl-option" class="switch" id="switch-use-tls" v-bind:checked="use_tls">
|
||||
<label for="switch-use-tls" v-if="use_tls">Custom TLS Certificates for mTLS services</label>
|
||||
|
@ -209,7 +209,7 @@
|
|||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Enable Notifications</label>
|
||||
<div class="col-8 mt-1">
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
<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>
|
||||
|
@ -226,7 +226,7 @@
|
|||
</div>
|
||||
<div v-if="service.allow_notifications" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Notify All Changes</label>
|
||||
<div class="col-8 mt-1">
|
||||
<div class="col-12 col-md-8 mt-1">
|
||||
<span @click="service.notify_all_changes = !!service.notify_all_changes" class="switch float-left">
|
||||
<input v-model="service.notify_all_changes" type="checkbox" name="notify_all-option" class="switch" id="notify_all" v-bind:checked="service.notify_all_changes">
|
||||
<label v-if="service.notify_all_changes" for="notify_all">Continuously send notifications when service is failing.</label>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="col-12">
|
||||
<form @submit.prevent="saveSetup">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="text-capitalize">{{ $t('setup.language') }}</label>
|
||||
<select @change="changeLanguages" v-model="setup.language" id="language" class="form-control">
|
||||
|
@ -27,13 +27,13 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="col-7 col-md-6">
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label class="text-capitalize">{{ $t('setup.host') }}</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_host" id="db_host" type="text" class="form-control" placeholder="localhost">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="col-5 col-md-6">
|
||||
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
|
||||
<label class="text-capitalize">{{ $t('port') }}</label>
|
||||
<input @keyup="canSubmit" v-model="setup.db_port" id="db_port" type="number" class="form-control" placeholder="5432">
|
||||
|
@ -65,12 +65,11 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="text-capitalize">{{ $t('setup.project_name') }}</label>
|
||||
|
@ -125,9 +124,11 @@
|
|||
{{error}}
|
||||
</div>
|
||||
|
||||
<button @click.prevent="saveSetup" v-bind:disabled="disabled || loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
|
||||
<font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/>{{loading ? "Loading..." : "Save Settings"}}
|
||||
</button>
|
||||
<div class="col-12">
|
||||
<button @click.prevent="saveSetup" v-bind:disabled="disabled || loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
|
||||
<font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/>{{loading ? "Loading..." : "Save Settings"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
@ -137,7 +138,6 @@
|
|||
|
||||
<script>
|
||||
import Api from "../API";
|
||||
import Index from "../pages/Index";
|
||||
|
||||
export default {
|
||||
name: 'Setup',
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
|
||||
<script>
|
||||
import Api from "../API";
|
||||
import LoadButton from "@/components/Elements/LoadButton";
|
||||
const LoadButton = () => import(/* webpackChunkName: "index" */ "@/components/Elements/LoadButton");
|
||||
|
||||
export default {
|
||||
name: 'FormUser',
|
||||
|
|
|
@ -40,8 +40,8 @@ const english = {
|
|||
wrong_login: 'Incorrect username or password'
|
||||
},
|
||||
settings: {
|
||||
name: "Project Name",
|
||||
description: "Project Name",
|
||||
name: "Site Name",
|
||||
description: "Site Description",
|
||||
footer: "Custom Footer",
|
||||
footer_notes: "HTML is allowed inside the footer",
|
||||
error_reporting: "Enable Error Reporting",
|
||||
|
|
|
@ -5,13 +5,18 @@ import VueObserveVisibility from 'vue-observe-visibility'
|
|||
import VueClipboard from 'vue-clipboard2'
|
||||
import VueCookies from 'vue-cookies'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import * as Integrations from "@sentry/integrations";
|
||||
import router from './routes'
|
||||
import "./mixin"
|
||||
import "./icons"
|
||||
const App = () => import('@/App.vue')
|
||||
import store from './store'
|
||||
import language from './languages'
|
||||
|
||||
const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping.com/3"
|
||||
|
||||
const App = () => import(/* webpackChunkName: "index" */ '@/App.vue')
|
||||
|
||||
Vue.component('apexchart', VueApexCharts)
|
||||
|
||||
Vue.use(VueClipboard);
|
||||
|
@ -27,6 +32,11 @@ const i18n = new VueI18n({
|
|||
|
||||
Vue.$cookies.config('3d')
|
||||
|
||||
Sentry.init({
|
||||
dsn: errorReporter,
|
||||
integrations: [new Integrations.Vue({Vue, attachProps: true, logErrors: true})],
|
||||
});
|
||||
|
||||
Vue.config.productionTip = false
|
||||
new Vue({
|
||||
router,
|
||||
|
|
|
@ -106,10 +106,6 @@ export default Vue.mixin({
|
|||
isAdmin() {
|
||||
return this.$store.state.admin
|
||||
},
|
||||
loggedIn() {
|
||||
const core = this.$store.getters.core
|
||||
return core.logged_in === true
|
||||
},
|
||||
iconName(name) {
|
||||
switch (name) {
|
||||
case "fas fa-terminal":
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
const TopNav = () => import('@/components/Dashboard/TopNav')
|
||||
const TopNav = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/TopNav')
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
|
@ -35,6 +35,3 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
export default {
|
||||
name: 'Help',
|
||||
}
|
||||
</script>
|
||||
</script>
|
|
@ -5,12 +5,12 @@
|
|||
|
||||
<div class="col-12 full-col-12">
|
||||
<div v-for="service in services_no_group" v-bind:key="service.id" class="list-group online_list mb-4">
|
||||
<a class="service_li list-group-item list-group-item-action">
|
||||
<div class="service_li list-group-item list-group-item-action">
|
||||
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
|
||||
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
||||
<GroupServiceFailures :service="service"/>
|
||||
<IncidentsBlock :service="service"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -32,22 +32,23 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
const Group = () => import('@/components/Index/Group')
|
||||
const Header = () => import('@/components/Index/Header')
|
||||
const MessageBlock = () => import('@/components/Index/MessageBlock')
|
||||
const ServiceBlock = () => import('@/components/Service/ServiceBlock')
|
||||
const GroupServiceFailures = () => import('@/components/Index/GroupServiceFailures')
|
||||
const IncidentsBlock = () => import('@/components/Index/IncidentsBlock')
|
||||
import Api from "@/API";
|
||||
const Group = () => import(/* webpackChunkName: "index" */ '@/components/Index/Group')
|
||||
const Header = () => import(/* webpackChunkName: "index" */ '@/components/Index/Header')
|
||||
const MessageBlock = () => import(/* webpackChunkName: "index" */ '@/components/Index/MessageBlock')
|
||||
const ServiceBlock = () => import(/* webpackChunkName: "index" */ '@/components/Service/ServiceBlock')
|
||||
const GroupServiceFailures = () => import(/* webpackChunkName: "index" */ '@/components/Index/GroupServiceFailures')
|
||||
const IncidentsBlock = () => import(/* webpackChunkName: "index" */ '@/components/Index/IncidentsBlock')
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
IncidentsBlock,
|
||||
GroupServiceFailures,
|
||||
ServiceBlock,
|
||||
MessageBlock,
|
||||
Group,
|
||||
Header
|
||||
ServiceBlock,
|
||||
MessageBlock,
|
||||
Group,
|
||||
Header
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -67,27 +68,27 @@ export default {
|
|||
services_no_group() {
|
||||
return this.$store.getters.servicesNoGroup
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.logged_in = this.loggedIn()
|
||||
},
|
||||
async mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async checkLogin() {
|
||||
const token = this.$cookies.get('statping_auth')
|
||||
if (!token) {
|
||||
this.$store.commit('setLoggedIn', false)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const jwt = await Api.check_token(token)
|
||||
this.$store.commit('setAdmin', jwt.admin)
|
||||
if (jwt.username) {
|
||||
this.$store.commit('setLoggedIn', true)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
inRange(message) {
|
||||
return this.isBetween(this.now(), message.start_on, message.start_on === message.end_on ? this.maxDate().toISOString() : message.end_on)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
const FormLogin = () => import('@/forms/Login')
|
||||
const FormLogin = () => import(/* webpackChunkName: "index" */ '@/forms/Login')
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
|
@ -22,7 +22,10 @@
|
|||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,10 +87,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.sm {
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,37 +1,35 @@
|
|||
<template>
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5">
|
||||
|
||||
<div class="col-12 mb-4">
|
||||
|
||||
<span class="mt-3 mb-3 text-white d-md-none btn d-block d-md-none text-uppercase" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||
{{service.online ? $t('online') : $t('offline')}}
|
||||
</span>
|
||||
|
||||
<h4 class="mt-2">
|
||||
<span class="mt-2 font-3">
|
||||
<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 text-uppercase" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||
{{service.online ? $t('online') : $t('offline')}}
|
||||
</span>
|
||||
</h4>
|
||||
</span>
|
||||
|
||||
<ServiceTopStats :service="service"/>
|
||||
<ServiceTopStats v-if="loaded" :service="service"/>
|
||||
|
||||
<MessageBlock v-for="message in messagesInRange" v-bind:key="message.id" :message="message"/>
|
||||
<MessageBlock v-if="loaded" v-for="message in messagesInRange" v-bind:key="message.id" :message="message"/>
|
||||
|
||||
<div class="card text-black-50 bg-white mt-3">
|
||||
<div class="card-header text-capitalize">Timeframe</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body pb-4">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 font-2">
|
||||
<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 />
|
||||
<div class="col-12 col-md-4 font-2 mb-3 mb-md-0">
|
||||
<flatPickr :disabled="!loaded" @on-change="reload" v-model="start_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="form-control text-left d-block" required />
|
||||
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 font-2">
|
||||
<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 />
|
||||
<div class="col-12 col-md-4 font-2 mb-3 mb-md-0">
|
||||
<flatPickr :disabled="!loaded" @on-change="reload" v-model="end_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date()}" type="text" class="form-control text-left" required />
|
||||
<small class="d-block">To {{this.format(new Date(end_time))}}</small>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<select :disabled="loading" @change="chartHits" v-model="group" class="form-control">
|
||||
<div class="col-12 col-md-4 mb-1 mb-md-0">
|
||||
<select :disabled="!loaded" @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>
|
||||
|
@ -50,10 +48,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
||||
<div class="card text-black-50 bg-white mt-3 mb-3">
|
||||
<div class="card-header text-capitalize">Service Latency</div>
|
||||
<div v-if="loaded" class="card-body">
|
||||
<div class="row mb-5">
|
||||
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<apexchart height="220" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row mt-3 mb-3">
|
||||
<div class="col-12 text-center">
|
||||
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading" class="row">
|
||||
<apexchart width="100%" height="120" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
||||
</div>
|
||||
|
||||
<div class="card text-black-50 bg-white mb-3">
|
||||
|
@ -72,12 +82,12 @@
|
|||
|
||||
<script>
|
||||
import Api from "../API"
|
||||
const MessageBlock = () => import('@/components/Index/MessageBlock')
|
||||
const ServiceFailures = () => import('@/components/Service/ServiceFailures')
|
||||
const Checkin = () => import('@/forms/Checkin')
|
||||
const ServiceHeatmap = () => import('@/components/Service/ServiceHeatmap')
|
||||
const ServiceTopStats = () => import('@/components/Service/ServiceTopStats')
|
||||
const AdvancedChart = () => import('@/components/Service/AdvancedChart')
|
||||
const MessageBlock = () => import(/* webpackChunkName: "index" */ '@/components/Index/MessageBlock')
|
||||
const ServiceFailures = () => import(/* webpackChunkName: "service" */ '@/components/Service/ServiceFailures')
|
||||
const Checkin = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Checkin')
|
||||
const ServiceHeatmap = () => import(/* webpackChunkName: "service" */ '@/components/Service/ServiceHeatmap')
|
||||
const ServiceTopStats = () => import(/* webpackChunkName: "service" */ '@/components/Service/ServiceTopStats')
|
||||
const AdvancedChart = () => import(/* webpackChunkName: "service" */ '@/components/Service/AdvancedChart')
|
||||
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
import 'flatpickr/dist/flatpickr.css';
|
||||
|
@ -120,13 +130,14 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
service: null,
|
||||
tab: "failures",
|
||||
authenticated: false,
|
||||
ready: true,
|
||||
group: "1h",
|
||||
data: null,
|
||||
uptime_data: null,
|
||||
loading: true,
|
||||
loaded: false,
|
||||
messages: [],
|
||||
failures: [],
|
||||
start_time: this.nowSubtract(84600 * 30),
|
||||
|
@ -144,6 +155,7 @@ export default {
|
|||
chart: {
|
||||
id: 'uptime',
|
||||
height: 120,
|
||||
width: "100%",
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: false
|
||||
|
@ -336,9 +348,6 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
service () {
|
||||
return this.$store.getters.serviceByAll(this.id)
|
||||
},
|
||||
core () {
|
||||
return this.$store.getters.core
|
||||
},
|
||||
|
@ -362,30 +371,36 @@ export default {
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
service: function(n, o) {
|
||||
this.onnn()
|
||||
},
|
||||
load_timedata: function(n, o) {
|
||||
this.onnn()
|
||||
'$route': 'reload',
|
||||
},
|
||||
created() {
|
||||
this.reload()
|
||||
},
|
||||
async mounted() {
|
||||
if (!this.$store.getters.service) {
|
||||
// const s = await Api.service(this.id)
|
||||
// this.$store.commit('setService', s)
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (!this.$store.getters.service) {
|
||||
const s = await Api.service(this.id)
|
||||
this.$store.commit('setService', s)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updated_chart(start, end) {
|
||||
this.loaded = false
|
||||
this.start_time = start
|
||||
this.end_time = end
|
||||
this.loading = false
|
||||
this.loaded = true
|
||||
},
|
||||
async onnn() {
|
||||
this.loading = true
|
||||
async reload() {
|
||||
this.loaded = false
|
||||
const services = await Api.services()
|
||||
this.$store.commit("setServices", services)
|
||||
if (this.isNumeric(this.$route.params.id)) {
|
||||
this.service = this.$store.getters.serviceById(this.$route.params.id)
|
||||
} else {
|
||||
this.service = this.$store.getters.serviceByPermalink(this.$route.params.id)
|
||||
}
|
||||
await this.chartHits()
|
||||
await this.fetchUptime()
|
||||
this.loading = false
|
||||
this.loaded = true
|
||||
},
|
||||
async fetchUptime() {
|
||||
const uptime = await Api.service_uptime(this.service.id, this.params.start, this.params.end)
|
||||
|
@ -395,7 +410,6 @@ export default {
|
|||
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({
|
||||
|
|
|
@ -119,12 +119,12 @@
|
|||
import GithubButton from 'vue-github-button'
|
||||
import Variables from "@/components/Dashboard/Variables";
|
||||
|
||||
const CoreSettings = () => import('@/forms/CoreSettings')
|
||||
const FormIntegration = () => import('@/forms/Integration')
|
||||
const Notifier = () => import('@/forms/Notifier')
|
||||
const OAuth = () => import('@/forms/OAuth')
|
||||
const ThemeEditor = () => import('@/components/Dashboard/ThemeEditor')
|
||||
const Cache = () => import('@/components/Dashboard/Cache')
|
||||
const CoreSettings = () => import(/* webpackChunkName: "dashboard" */ '@/forms/CoreSettings')
|
||||
const FormIntegration = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Integration')
|
||||
const Notifier = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Notifier')
|
||||
const OAuth = () => import(/* webpackChunkName: "dashboard" */ '@/forms/OAuth')
|
||||
const ThemeEditor = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ThemeEditor')
|
||||
const Cache = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/Cache')
|
||||
|
||||
export default {
|
||||
name: 'Settings',
|
||||
|
@ -152,10 +152,10 @@
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.update()
|
||||
|
||||
},
|
||||
created() {
|
||||
this.update()
|
||||
this.update()
|
||||
},
|
||||
methods: {
|
||||
async update() {
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
const Index = () => import('@/pages/Index')
|
||||
const Dashboard = () => import('@/pages/Dashboard')
|
||||
const DashboardIndex = () => import('@/components/Dashboard/DashboardIndex')
|
||||
const DashboardUsers = () => import('@/components/Dashboard/DashboardUsers')
|
||||
const DashboardServices = () => import('@/components/Dashboard/DashboardServices')
|
||||
const DashboardMessages = () => import('@/components/Dashboard/DashboardMessages')
|
||||
const EditService = () => import('@/components/Dashboard/EditService')
|
||||
const Logs = () => import('@/pages/Logs')
|
||||
const Settings = () => import('@/pages/Settings')
|
||||
const Login = () => import('@/pages/Login')
|
||||
const Service = () => import('@/pages/Service')
|
||||
const Setup = () => import('@/forms/Setup')
|
||||
const Incidents = () => import('@/components/Dashboard/Incidents')
|
||||
const Checkins = () => import('@/components/Dashboard/Checkins')
|
||||
const Failures = () => import('@/components/Dashboard/Failures')
|
||||
const NotFound = () => import('@/pages/NotFound')
|
||||
const Index = () => import(/* webpackChunkName: "index" */ '@/pages/Index')
|
||||
const Dashboard = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Dashboard')
|
||||
const DashboardIndex = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardIndex')
|
||||
const DashboardUsers = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardUsers')
|
||||
const DashboardServices = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardServices')
|
||||
const DashboardMessages = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardMessages')
|
||||
const EditService = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/EditService')
|
||||
const Logs = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Logs')
|
||||
const Settings = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Settings')
|
||||
const Login = () => import(/* webpackChunkName: "index" */ '@/pages/Login')
|
||||
const Service = () => import(/* webpackChunkName: "index" */ '@/pages/Service')
|
||||
const Setup = () => import(/* webpackChunkName: "index" */ '@/forms/Setup')
|
||||
const Incidents = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/Incidents')
|
||||
const Checkins = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/Checkins')
|
||||
const Failures = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/Failures')
|
||||
const NotFound = () => import(/* webpackChunkName: "index" */ '@/pages/NotFound')
|
||||
|
||||
import VueRouter from "vue-router";
|
||||
import Api from "./API";
|
||||
import store from "./store"
|
||||
|
||||
const Loading = {
|
||||
template: '<div class="jumbotron">LOADING</div>'
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/setup',
|
||||
name: 'Setup',
|
||||
component: Setup
|
||||
component: Setup,
|
||||
meta: {
|
||||
title: 'Statping Setup',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
|
@ -33,14 +41,37 @@ const routes = [
|
|||
path: '/dashboard',
|
||||
component: Dashboard,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Dashboard',
|
||||
},
|
||||
beforeEnter: async (to, from, next) => {
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
let tk = await Api.token()
|
||||
if (to.path !== '/login' && !tk) {
|
||||
next('/login')
|
||||
return
|
||||
if (to.path !== '/login') {
|
||||
if(store.getters.loggedIn) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const token = $cookies.get('statping_auth')
|
||||
if (!token) {
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const jwt = await Api.check_token(token)
|
||||
store.commit('setAdmin', jwt.admin)
|
||||
if (jwt.admin) {
|
||||
store.commit('setLoggedIn', true)
|
||||
store.commit('setUser', true)
|
||||
} else {
|
||||
store.commit('setLoggedIn', false)
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
}
|
||||
next()
|
||||
} else {
|
||||
|
@ -51,80 +82,96 @@ const routes = [
|
|||
path: '',
|
||||
component: DashboardIndex,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Dashboard',
|
||||
}
|
||||
},{
|
||||
path: 'users',
|
||||
component: DashboardUsers,
|
||||
loading: Loading,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Users',
|
||||
}
|
||||
},{
|
||||
path: 'services',
|
||||
component: DashboardServices,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Services',
|
||||
}
|
||||
},{
|
||||
path: 'create_service',
|
||||
component: EditService,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Create Service',
|
||||
}
|
||||
},{
|
||||
path: 'edit_service/:id',
|
||||
component: EditService,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Edit Service',
|
||||
}
|
||||
},{
|
||||
path: 'service/:id/incidents',
|
||||
component: Incidents,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Incidents',
|
||||
}
|
||||
},{
|
||||
path: 'service/:id/checkins',
|
||||
component: Checkins,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Checkins',
|
||||
}
|
||||
},{
|
||||
path: 'service/:id/failures',
|
||||
component: Failures,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Service Failures',
|
||||
}
|
||||
},{
|
||||
path: 'messages',
|
||||
component: DashboardMessages,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Messages',
|
||||
}
|
||||
},{
|
||||
path: 'settings',
|
||||
component: Settings,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Settings',
|
||||
}
|
||||
},{
|
||||
path: 'logs',
|
||||
component: Logs,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Logs',
|
||||
}
|
||||
},{
|
||||
path: 'help',
|
||||
component: Logs,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
requiresAuth: true,
|
||||
title: 'Statping - Help',
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
component: Login,
|
||||
meta: {
|
||||
title: 'Statping - Login',
|
||||
}
|
||||
},
|
||||
{ path: '/logout', redirect: '/' },
|
||||
{
|
||||
|
@ -152,23 +199,23 @@ const router = new VueRouter({
|
|||
routes
|
||||
})
|
||||
|
||||
let CheckAuth = (to, from, next) => {
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
let item = this.$cookies.get("statping_auth")
|
||||
window.console.log(item)
|
||||
if (to.path !== '/login' && !item) {
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
const auth = JSON.parse(item)
|
||||
if (!auth.token) {
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
next()
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
router.beforeEach((to, from, next) => {
|
||||
const nearestWithTitle = to.matched.slice().reverse().find(r => r.meta && r.meta.title);
|
||||
const nearestWithMeta = to.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
|
||||
const previousNearestWithMeta = from.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
|
||||
if(nearestWithTitle) document.title = nearestWithTitle.meta.title;
|
||||
Array.from(document.querySelectorAll('[data-vue-router-controlled]')).map(el => el.parentNode.removeChild(el));
|
||||
if(!nearestWithMeta) return next();
|
||||
nearestWithMeta.meta.metaTags.map(tagDef => {
|
||||
const tag = document.createElement('meta');
|
||||
Object.keys(tagDef).forEach(key => {
|
||||
tag.setAttribute(key, tagDef[key]);
|
||||
});
|
||||
tag.setAttribute('data-vue-router-controlled', '');
|
||||
return tag;
|
||||
})
|
||||
.forEach(tag => document.head.appendChild(tag));
|
||||
next();
|
||||
});
|
||||
|
||||
export default router
|
||||
|
|
|
@ -30,7 +30,8 @@ export default new Vuex.Store({
|
|||
notifiers: [],
|
||||
checkins: [],
|
||||
admin: false,
|
||||
user: false
|
||||
user: false,
|
||||
loggedIn: false
|
||||
},
|
||||
getters: {
|
||||
hasAllData: state => state.hasAllData,
|
||||
|
@ -46,6 +47,7 @@ export default new Vuex.Store({
|
|||
users: state => state.users,
|
||||
notifiers: state => state.notifiers,
|
||||
checkins: state => state.checkins,
|
||||
loggedIn: state => state.loggedIn,
|
||||
|
||||
isAdmin: state => state.admin,
|
||||
isUser: state => state.user,
|
||||
|
@ -61,13 +63,13 @@ export default new Vuex.Store({
|
|||
},
|
||||
serviceByAll: (state) => (element) => {
|
||||
if (element % 1 === 0) {
|
||||
return state.services.find(s => s.id == element)
|
||||
return state.services.find(s => s.id === element)
|
||||
} else {
|
||||
return state.services.find(s => s.permalink === element)
|
||||
}
|
||||
},
|
||||
serviceById: (state) => (id) => {
|
||||
return state.services.find(s => s.id == id)
|
||||
return state.services.find(s => s.id === id)
|
||||
},
|
||||
serviceByPermalink: (state) => (permalink) => {
|
||||
return state.services.find(s => s.permalink === permalink)
|
||||
|
@ -131,6 +133,9 @@ export default new Vuex.Store({
|
|||
setAdmin (state, admin) {
|
||||
state.admin = admin
|
||||
},
|
||||
setLoggedIn (state, loggedIn) {
|
||||
state.loggedIn = loggedIn
|
||||
},
|
||||
setUser (state, user) {
|
||||
state.user = user
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
8
go.mod
8
go.mod
|
@ -9,9 +9,9 @@ require (
|
|||
github.com/fatih/structs v1.1.0
|
||||
github.com/foomo/simplecert v1.7.5
|
||||
github.com/foomo/tlsconfig v0.0.0-20180418120404-b67861b076c9
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/getsentry/sentry-go v0.5.1
|
||||
github.com/go-mail/mail v2.3.1+incompatible
|
||||
github.com/golang/protobuf v1.4.0
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9
|
||||
github.com/jinzhu/gorm v1.9.12
|
||||
|
@ -24,12 +24,14 @@ require (
|
|||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a
|
||||
github.com/tdewolff/minify/v2 v2.8.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
|
||||
google.golang.org/grpc v1.28.1
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/mail.v2 v2.3.1 // indirect
|
||||
|
|
19
go.sum
19
go.sum
|
@ -114,6 +114,7 @@ github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8
|
|||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
@ -357,6 +358,7 @@ github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6i
|
|||
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d h1:V5Rs9ztEWdp58oayPq/ulmlqJJZeJP6pP79uP3qjcao=
|
||||
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.0 h1:GhthINjveNZAdFUD8QoQYfjxnOONZgztK/Yr6M23UTY=
|
||||
|
@ -396,6 +398,7 @@ github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVL
|
|||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
|
@ -573,6 +576,12 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
|||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1 h1:HGVkRrwDCbmSP6h1CoBDj6l/mhnvsP5JbYaQ4ss0R6o=
|
||||
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1/go.mod h1:I3xbaE9ud9/TEXzehwkHx86SyJwqeSNsX2X5oV61jIg=
|
||||
github.com/tdewolff/minify/v2 v2.8.0 h1:t3tOPWkTpKhsgxm3IM9Sy8hE2eIt30Oaa+2havJGGIE=
|
||||
github.com/tdewolff/minify/v2 v2.8.0/go.mod h1:6zN8VLhMfFxNrwHROcboYNo2+huPNu4SV8DPh3PUQ8E=
|
||||
github.com/tdewolff/parse/v2 v2.4.4 h1:uMdbQRtYbKR/msP9CbI7li9wK6pionYiH6s7ipltyGY=
|
||||
github.com/tdewolff/parse/v2 v2.4.4/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
|
@ -641,6 +650,8 @@ golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+
|
|||
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo=
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -662,6 +673,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
|||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
|
@ -704,6 +716,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -719,6 +733,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -759,6 +775,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -808,6 +826,7 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
|
|||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4 h1:kDtqNkeBrZb8B+atrj50B5XLHpzXXqcCdZPP/ApQ5NY=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -112,6 +112,9 @@ func TestSetupRoutes(t *testing.T) {
|
|||
if !core.App.Setup {
|
||||
return errors.New("core has not been setup")
|
||||
}
|
||||
if core.App.ApiSecret == "" {
|
||||
return errors.New("API Key has not been set")
|
||||
}
|
||||
if len(services.AllInOrder()) == 0 {
|
||||
return errors.New("no services where found")
|
||||
}
|
||||
|
|
|
@ -84,19 +84,35 @@ func apiThemeSaveHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
fmt.Println(themes.Variables)
|
||||
|
||||
if err := source.SaveAsset([]byte(themes.Base), "scss/base.scss"); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.SaveAsset([]byte(themes.Layout), "scss/layout.scss"); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.SaveAsset([]byte(themes.Variables), "scss/variables.scss"); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.SaveAsset([]byte(themes.Forms), "scss/forms.scss"); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.SaveAsset([]byte(themes.Mixins), "scss/mixin.scss"); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.SaveAsset([]byte(themes.Mobile), "scss/mobile.scss"); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.CompileSASS(source.DefaultScss...); err != nil {
|
||||
if err := source.CompileSASS(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
@ -114,14 +130,14 @@ func apiThemeCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
utils.Log.Infof("creating assets in folder: %s/%s", dir, "assets")
|
||||
if err := source.CreateAllAssets(dir); err != nil {
|
||||
log.Errorln(err)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.CompileSASS(source.DefaultScss...); err != nil {
|
||||
source.CopyToPublic(source.TmplBox, "css", "main.css")
|
||||
source.CopyToPublic(source.TmplBox, "css", "base.css")
|
||||
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
|
||||
if err := source.CopyToPublic(source.TmplBox, "css", "style.css"); err != nil {
|
||||
log.Errorln(err)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
} else {
|
||||
log.Errorln(err)
|
||||
sendErrorJson(err, w, r)
|
||||
}
|
||||
}
|
||||
resetRouter()
|
||||
sendJsonAction(dir+"/assets", "created", w, r)
|
||||
|
|
|
@ -141,7 +141,7 @@ func TestGroupAPIRoutes(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Name: "Statping View Unknown Group",
|
||||
URL: "/api/groups/8383883838",
|
||||
URL: "/api/groups/38383",
|
||||
Method: "GET",
|
||||
BeforeTest: SetTestENV,
|
||||
ExpectedStatus: 404,
|
||||
|
|
|
@ -45,7 +45,7 @@ func apiIncidentUpdatesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
returnJson(incid.Updates(), w, r)
|
||||
returnJson(incid.Updates, w, r)
|
||||
}
|
||||
|
||||
func apiCreateIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -100,8 +100,7 @@ func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
updates := incident.Updates()
|
||||
sendJsonAction(updates, "update", w, r)
|
||||
sendJsonAction(incident.Updates, "update", w, r)
|
||||
}
|
||||
|
||||
func apiDeleteIncidentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -22,3 +22,8 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
returnJson(health, w, r)
|
||||
}
|
||||
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
ExecuteResponse(w, r, "base.gohtml", core.App, nil)
|
||||
}
|
||||
|
|
|
@ -51,17 +51,9 @@ func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
|
|||
return jwtClaim, tokenString
|
||||
}
|
||||
|
||||
func getJwtToken(r *http.Request) (JwtClaim, error) {
|
||||
c, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
if err == http.ErrNoCookie {
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
|
||||
func parseToken(token string) (JwtClaim, error) {
|
||||
var claims JwtClaim
|
||||
tkn, err := jwt.ParseWithClaims(c.Value, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||
tkn, err := jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return jwtKey, nil
|
||||
})
|
||||
|
||||
|
@ -74,5 +66,16 @@ func getJwtToken(r *http.Request) (JwtClaim, error) {
|
|||
if !tkn.Valid {
|
||||
return claims, errors.New("token is not valid")
|
||||
}
|
||||
return claims, err
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func getJwtToken(r *http.Request) (JwtClaim, error) {
|
||||
c, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
if err == http.ErrNoCookie {
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
return parseToken(c.Value)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/statping/statping/types/errors"
|
||||
"github.com/statping/statping/types/failures"
|
||||
"github.com/statping/statping/types/notifications"
|
||||
"github.com/statping/statping/types/services"
|
||||
|
@ -11,12 +12,10 @@ import (
|
|||
|
||||
func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var notifs []notifications.Notification
|
||||
notifiers := services.AllNotifiers()
|
||||
for _, n := range notifiers {
|
||||
notif := n.Select()
|
||||
notifer, _ := notifications.Find(notif.Method)
|
||||
notif.UpdateFields(notifer)
|
||||
notifs = append(notifs, *notif)
|
||||
for _, n := range services.AllNotifiers() {
|
||||
no := n.Select()
|
||||
notif, _ := notifications.Find(no.Method)
|
||||
notifs = append(notifs, *no.UpdateFields(notif))
|
||||
}
|
||||
sort.Sort(notifications.NotificationOrder(notifs))
|
||||
returnJson(notifs, w, r)
|
||||
|
@ -24,10 +23,9 @@ func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
notif := services.FindNotifier(vars["notifier"])
|
||||
notifer, err := notifications.Find(notif.Method)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
notifer := services.FindNotifier(vars["notifier"])
|
||||
if notifer == nil {
|
||||
sendErrorJson(errors.New("could not find notifier"), w, r)
|
||||
return
|
||||
}
|
||||
returnJson(notifer, w, r)
|
||||
|
@ -48,13 +46,17 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
log.Infof("Updating %s Notifier", notifer.Title)
|
||||
|
||||
err = notifer.Update()
|
||||
if err != nil {
|
||||
if err := notifer.Update(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
notif := services.ReturnNotifier(notifer.Method)
|
||||
if err := notif.Valid(notifer.Values()); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := notif.OnSave(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
|
@ -70,9 +72,9 @@ type testNotificationReq struct {
|
|||
|
||||
func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
n, err := notifications.Find(vars["notifier"])
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
n := services.FindNotifier(vars["notifier"])
|
||||
if n == nil {
|
||||
sendErrorJson(errors.New("unknown notifier"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -85,6 +87,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
|||
notif := services.ReturnNotifier(n.Method)
|
||||
|
||||
var out string
|
||||
var err error
|
||||
if req.Method == "success" {
|
||||
out, err = notif.OnSuccess(services.Example(true))
|
||||
} else {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -12,7 +13,7 @@ func TestAttachment(t *testing.T) {
|
|||
notifiers.InitNotifiers()
|
||||
}
|
||||
|
||||
func TestUnAuthenticatedNotifierRoutes(t *testing.T) {
|
||||
func TestAuthenticatedNotifierRoutes(t *testing.T) {
|
||||
slackWebhookUrl := utils.Params.GetString("SLACK_URL")
|
||||
|
||||
tests := []HTTPTest{
|
||||
|
@ -42,7 +43,12 @@ func TestUnAuthenticatedNotifierRoutes(t *testing.T) {
|
|||
URL: "/api/notifier/slack",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
BeforeTest: SetTestENV,
|
||||
BeforeTest: func(t *testing.T) error {
|
||||
notif := services.FindNotifier("slack")
|
||||
require.NotNil(t, notif)
|
||||
assert.Equal(t, "slack", notif.Method)
|
||||
return SetTestENV(t)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "No Authentication - Update Notifier",
|
||||
|
@ -68,7 +74,20 @@ func TestUnAuthenticatedNotifierRoutes(t *testing.T) {
|
|||
"limits": 55
|
||||
}`,
|
||||
ExpectedStatus: 200,
|
||||
BeforeTest: SetTestENV,
|
||||
BeforeTest: func(t *testing.T) error {
|
||||
notif := services.FindNotifier("slack")
|
||||
require.NotNil(t, notif)
|
||||
assert.Equal(t, "slack", notif.Method)
|
||||
assert.False(t, notif.Enabled.Bool)
|
||||
return SetTestENV(t)
|
||||
},
|
||||
AfterTest: func(t *testing.T) error {
|
||||
notif := services.FindNotifier("slack")
|
||||
require.NotNil(t, notif)
|
||||
assert.Equal(t, "slack", notif.Method)
|
||||
assert.True(t, notif.Enabled.Bool)
|
||||
return UnsetTestENV(t)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Test Notifier (OnSuccess)",
|
||||
|
@ -82,7 +101,7 @@ func TestUnAuthenticatedNotifierRoutes(t *testing.T) {
|
|||
"method": "slack",
|
||||
"host": "` + slackWebhookUrl + `",
|
||||
"success_data": "{\n \"blocks\": [{\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"The service {{.Service.Name}} is back online.\"\n }\n }, {\n \"type\": \"actions\",\n \"elements\": [{\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View Service\",\n \"emoji\": true\n },\n \"style\": \"primary\",\n \"url\": \"{{.Core.Domain}}/service/{{.Service.Id}}\"\n }, {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Go to Statping\",\n \"emoji\": true\n },\n \"url\": \"{{.Core.Domain}}\"\n }]\n }]\n}",
|
||||
"failure_data": "{\n \"blocks\": [{\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \":warning: The service {{.Service.Name}} is currently offline! :warning:\"\n }\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"section\",\n \"fields\": [{\n \"type\": \"mrkdwn\",\n \"text\": \"*Service:*\\n{{.Service.Name}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*URL:*\\n{{.Service.Domain}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Status Code:*\\n{{.Service.LastStatusCode}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*When:*\\n{{.Failure.CreatedAt}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Downtime:*\\n{{.Service.DowntimeAgo}}\"\n }, {\n \"type\": \"plain_text\",\n \"text\": \"*Error:*\\n{{.Failure.Issue}}\"\n }]\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"actions\",\n \"elements\": [{\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View Offline Service\",\n \"emoji\": true\n },\n \"style\": \"danger\",\n \"url\": \"{{.Core.Domain}}/service/{{.Service.Id}}\"\n }, {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Go to Statping\",\n \"emoji\": true\n },\n \"url\": \"{{.Core.Domain}}\"\n }]\n }]\n}"
|
||||
"failure_data": "{\n \"blocks\": [{\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \":warning: The service {{.Service.Name}} is currently offline! :warning:\"\n }\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"section\",\n \"fields\": [{\n \"type\": \"mrkdwn\",\n \"text\": \"*Service:*\\n{{.Service.Name}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*URL:*\\n{{.Service.Domain}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Status Code:*\\n{{.Service.LastStatusCode}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*When:*\\n{{.Failure.CreatedAt}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Downtime:*\\n{{.Service.Downtime.Human}}\"\n }, {\n \"type\": \"plain_text\",\n \"text\": \"*Error:*\\n{{.Failure.Issue}}\"\n }]\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"actions\",\n \"elements\": [{\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View Offline Service\",\n \"emoji\": true\n },\n \"style\": \"danger\",\n \"url\": \"{{.Core.Domain}}/service/{{.Service.Id}}\"\n }, {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Go to Statping\",\n \"emoji\": true\n },\n \"url\": \"{{.Core.Domain}}\"\n }]\n }]\n}"
|
||||
}
|
||||
}`,
|
||||
ExpectedStatus: 200,
|
||||
|
@ -101,7 +120,7 @@ func TestUnAuthenticatedNotifierRoutes(t *testing.T) {
|
|||
"method": "slack",
|
||||
"host": "` + slackWebhookUrl + `",
|
||||
"success_data": "{\n \"blocks\": [{\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"The service {{.Service.Name}} is back online.\"\n }\n }, {\n \"type\": \"actions\",\n \"elements\": [{\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View Service\",\n \"emoji\": true\n },\n \"style\": \"primary\",\n \"url\": \"{{.Core.Domain}}/service/{{.Service.Id}}\"\n }, {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Go to Statping\",\n \"emoji\": true\n },\n \"url\": \"{{.Core.Domain}}\"\n }]\n }]\n}",
|
||||
"failure_data": "{\n \"blocks\": [{\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \":warning: The service {{.Service.Name}} is currently offline! :warning:\"\n }\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"section\",\n \"fields\": [{\n \"type\": \"mrkdwn\",\n \"text\": \"*Service:*\\n{{.Service.Name}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*URL:*\\n{{.Service.Domain}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Status Code:*\\n{{.Service.LastStatusCode}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*When:*\\n{{.Failure.CreatedAt}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Downtime:*\\n{{.Service.DowntimeAgo}}\"\n }, {\n \"type\": \"plain_text\",\n \"text\": \"*Error:*\\n{{.Failure.Issue}}\"\n }]\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"actions\",\n \"elements\": [{\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View Offline Service\",\n \"emoji\": true\n },\n \"style\": \"danger\",\n \"url\": \"{{.Core.Domain}}/service/{{.Service.Id}}\"\n }, {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Go to Statping\",\n \"emoji\": true\n },\n \"url\": \"{{.Core.Domain}}\"\n }]\n }]\n}"
|
||||
"failure_data": "{\n \"blocks\": [{\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \":warning: The service {{.Service.Name}} is currently offline! :warning:\"\n }\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"section\",\n \"fields\": [{\n \"type\": \"mrkdwn\",\n \"text\": \"*Service:*\\n{{.Service.Name}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*URL:*\\n{{.Service.Domain}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Status Code:*\\n{{.Service.LastStatusCode}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*When:*\\n{{.Failure.CreatedAt}}\"\n }, {\n \"type\": \"mrkdwn\",\n \"text\": \"*Downtime:*\\n{{.Service.Downtime.Human}}\"\n }, {\n \"type\": \"plain_text\",\n \"text\": \"*Error:*\\n{{.Failure.Issue}}\"\n }]\n }, {\n \"type\": \"divider\"\n }, {\n \"type\": \"actions\",\n \"elements\": [{\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View Offline Service\",\n \"emoji\": true\n },\n \"style\": \"danger\",\n \"url\": \"{{.Core.Domain}}/service/{{.Service.Id}}\"\n }, {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Go to Statping\",\n \"emoji\": true\n },\n \"url\": \"{{.Core.Domain}}\"\n }]\n }]\n}"
|
||||
}
|
||||
}`,
|
||||
ExpectedStatus: 200,
|
||||
|
@ -154,7 +173,7 @@ func TestApiNotifiersRoutes(t *testing.T) {
|
|||
URL: "/api/notifier/slack",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContains: []string{`"method":"slack"`},
|
||||
ExpectedContains: []string{`"method":"slack"`, `"host":"https://slack.api/example/12345"`},
|
||||
BeforeTest: SetTestENV,
|
||||
SecureRoute: true,
|
||||
},
|
||||
|
|
|
@ -69,24 +69,22 @@ func Router() *mux.Router {
|
|||
if source.UsingAssets(dir) {
|
||||
indexHandler := http.FileServer(http.Dir(dir + "/assets/"))
|
||||
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath, Gzip(staticAssets("css"))))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath, Gzip(staticAssets("js"))))
|
||||
r.PathPrefix("/scss/").Handler(http.StripPrefix(basePath, Gzip(staticAssets("scss"))))
|
||||
r.PathPrefix("/favicon/").Handler(http.StripPrefix(basePath, Gzip(staticAssets("favicon"))))
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath, staticAssets("css")))
|
||||
r.PathPrefix("/favicon/").Handler(http.StripPrefix(basePath, staticAssets("favicon")))
|
||||
r.PathPrefix("/robots.txt").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
r.PathPrefix("/banner.png").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
} else {
|
||||
tmplFileSrv := http.FileServer(source.TmplBox.HTTPBox())
|
||||
tmplBoxHandler := http.StripPrefix(basePath, tmplFileSrv)
|
||||
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath, Gzip(tmplFileSrv)))
|
||||
r.PathPrefix("/scss/").Handler(http.StripPrefix(basePath, Gzip(tmplFileSrv)))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath, Gzip(tmplFileSrv)))
|
||||
r.PathPrefix("/favicon/").Handler(http.StripPrefix(basePath, Gzip(tmplFileSrv)))
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath, tmplFileSrv))
|
||||
r.PathPrefix("/favicon/").Handler(http.StripPrefix(basePath, tmplFileSrv))
|
||||
r.PathPrefix("/robots.txt").Handler(tmplBoxHandler)
|
||||
r.PathPrefix("/banner.png").Handler(tmplBoxHandler)
|
||||
}
|
||||
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
|
||||
api := r.NewRoute().Subrouter()
|
||||
api.Use(apiMiddleware)
|
||||
api.Use(prometheusMiddleware)
|
||||
|
@ -154,6 +152,7 @@ func Router() *mux.Router {
|
|||
// API USER Routes
|
||||
api.Handle("/api/users", authenticated(apiAllUsersHandler, false)).Methods("GET")
|
||||
api.Handle("/api/users", authenticated(apiCreateUsersHandler, false)).Methods("POST")
|
||||
api.Handle("/api/users/token", http.HandlerFunc(apiCheckUserTokenHandler)).Methods("POST")
|
||||
api.Handle("/api/users/{id}", authenticated(apiUserHandler, false)).Methods("GET")
|
||||
api.Handle("/api/users/{id}", authenticated(apiUserUpdateHandler, false)).Methods("POST")
|
||||
api.Handle("/api/users/{id}", authenticated(apiUserDeleteHandler, false)).Methods("DELETE")
|
||||
|
@ -181,11 +180,12 @@ func Router() *mux.Router {
|
|||
// API Generic Routes
|
||||
r.Handle("/metrics", readOnly(promhttp.Handler(), false))
|
||||
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
|
||||
r.NotFoundHandler = http.HandlerFunc(error404Handler)
|
||||
r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||
return r
|
||||
}
|
||||
|
||||
func resetRouter() {
|
||||
log.Infoln("Restarting HTTP Router")
|
||||
router = Router()
|
||||
httpServer.Handler = router
|
||||
}
|
||||
|
|
|
@ -269,8 +269,7 @@ func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
err = service.DeleteFailures()
|
||||
if err != nil {
|
||||
if err := service.AllFailures().DeleteAll(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -123,7 +122,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
CacheStorage.Delete("/")
|
||||
resetCookies()
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
out := struct {
|
||||
Message string `json:"message"`
|
||||
Config *configs.DbConfig `json:"config"`
|
||||
|
|
|
@ -80,6 +80,23 @@ func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
|
|||
returnJson(allUsers, w, r)
|
||||
}
|
||||
|
||||
func apiCheckUserTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
token := r.PostForm.Get("token")
|
||||
if token == "" {
|
||||
sendErrorJson(errors.New("missing token parameter"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
claim, err := parseToken(token)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
returnJson(claim, w, r)
|
||||
}
|
||||
|
||||
func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var user *users.User
|
||||
err := DecodeJSON(r, &user)
|
||||
|
|
|
@ -22,6 +22,10 @@ func (c *commandLine) Select() *notifications.Notification {
|
|||
return c.Notification
|
||||
}
|
||||
|
||||
func (c *commandLine) Valid(values notifications.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var Command = &commandLine{¬ifications.Notification{
|
||||
Method: "command",
|
||||
Title: "Command",
|
||||
|
|
|
@ -51,6 +51,10 @@ func (d *discord) Select() *notifications.Notification {
|
|||
return d.Notification
|
||||
}
|
||||
|
||||
func (d *discord) Valid(values notifications.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnFailure will trigger failing service
|
||||
func (d *discord) OnFailure(s services.Service, f failures.Failure) (string, error) {
|
||||
out, err := d.sendRequest(ReplaceVars(d.FailureData.String, s, f))
|
||||
|
|
|
@ -28,6 +28,10 @@ func (e *emailer) Select() *notifications.Notification {
|
|||
return e.Notification
|
||||
}
|
||||
|
||||
func (e *emailer) Valid(values notifications.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var email = &emailer{¬ifications.Notification{
|
||||
Method: "email",
|
||||
Title: "SMTP Mail",
|
||||
|
@ -88,7 +92,7 @@ type emailOutgoing struct {
|
|||
// OnFailure will trigger failing service
|
||||
func (e *emailer) OnFailure(s services.Service, f failures.Failure) (string, error) {
|
||||
subject := fmt.Sprintf("Service %s is Offline", s.Name)
|
||||
tmpl := renderEmail(s, f)
|
||||
tmpl := renderEmail(s, f, emailFailure)
|
||||
email := &emailOutgoing{
|
||||
To: e.Var2.String,
|
||||
Subject: subject,
|
||||
|
@ -101,7 +105,7 @@ func (e *emailer) OnFailure(s services.Service, f failures.Failure) (string, err
|
|||
// OnSuccess will trigger successful service
|
||||
func (e *emailer) OnSuccess(s services.Service) (string, error) {
|
||||
subject := fmt.Sprintf("Service %s is Back Online", s.Name)
|
||||
tmpl := renderEmail(s, failures.Failure{})
|
||||
tmpl := renderEmail(s, failures.Failure{}, emailSuccess)
|
||||
email := &emailOutgoing{
|
||||
To: e.Var2.String,
|
||||
Subject: subject,
|
||||
|
@ -111,13 +115,13 @@ func (e *emailer) OnSuccess(s services.Service) (string, error) {
|
|||
return tmpl, e.dialSend(email)
|
||||
}
|
||||
|
||||
func renderEmail(s services.Service, f failures.Failure) string {
|
||||
func renderEmail(s services.Service, f failures.Failure, emailData string) string {
|
||||
wr := bytes.NewBuffer(nil)
|
||||
tmpl := template.New("email")
|
||||
tmpl, err := tmpl.Parse(emailBase)
|
||||
tmpl, err := tmpl.Parse(emailData)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return emailBase
|
||||
return emailData
|
||||
}
|
||||
|
||||
data := replacer{
|
||||
|
@ -129,7 +133,7 @@ func renderEmail(s services.Service, f failures.Failure) string {
|
|||
|
||||
if err = tmpl.ExecuteTemplate(wr, "email", data); err != nil {
|
||||
log.Errorln(err)
|
||||
return emailBase
|
||||
return emailData
|
||||
}
|
||||
|
||||
return wr.String()
|
||||
|
@ -142,7 +146,7 @@ func (e *emailer) OnTest() (string, error) {
|
|||
email := &emailOutgoing{
|
||||
To: e.Var2.String,
|
||||
Subject: subject,
|
||||
Template: renderEmail(service, failures.Example()),
|
||||
Template: renderEmail(service, failures.Example(), emailFailure),
|
||||
From: e.Var1.String,
|
||||
}
|
||||
return subject, e.dialSend(email)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,628 +0,0 @@
|
|||
package notifiers
|
||||
|
||||
const emailBase = `
|
||||
{{$banner := "https://assets.statping.com/greenbackground.png"}}
|
||||
{{$color := "#4caf50"}}
|
||||
{{if not .Service.Online}}
|
||||
{{$banner = "https://assets.statping.com/offlinebanner.png"}}
|
||||
{{$color = "#c30c0c"}}
|
||||
{{end}}
|
||||
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title> Statping Service Notification </title>
|
||||
<!--[if !mso]><!-- -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#E7E7E7;">
|
||||
<div style="background-color:#E7E7E7;">
|
||||
<!-- Top Bar -->
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
|
||||
<v:rect style="width:600px;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
|
||||
<v:fill origin="0.5, 0" position="0.5, 0" src="{{$banner}}" color="#FF3FB4" type="tile" />
|
||||
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
|
||||
<![endif]-->
|
||||
<div style="background:#FF3FB4 url({{$banner}}) top center / auto repeat;margin:0px auto;max-width:600px;">
|
||||
<div style="line-height:0;font-size:0;">
|
||||
<table align="center" background="{{$banner}}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FF3FB4 url({{$banner}}) top center / auto repeat;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:45px;"> <a href="https://statping.com" target="_blank">
|
||||
|
||||
<img
|
||||
alt="Statping" height="auto" src="https://assets.statping.com/iconlight.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="45"
|
||||
/>
|
||||
|
||||
</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</v:textbox>
|
||||
</v:rect>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:15px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:22px;line-height:30px;text-align:left;color:#000000;">
|
||||
|
||||
{{if .Service.Online}}
|
||||
{{.Service.Name}} is back online.
|
||||
{{else}}
|
||||
{{.Service.Name}} is currently offline, you might want to check it.
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:0px;padding:20px 0;padding-top:10px;padding-right:0px;padding-bottom:10px;padding-left:0px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:10px;padding-left:0px;padding-right:0px;padding-top:10px;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#626262;">
|
||||
{{if .Service.Online}}
|
||||
Online for {{.Service.Uptime.Human}}
|
||||
{{else}}
|
||||
Offline for {{.Service.Downtime.Human}}
|
||||
{{end}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="{{$color}}" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:{{$color}};" valign="middle">
|
||||
<a href="{{.Core.Domain}}/service/{{.Service.Id}}" style="display:inline-block;background:{{$color}};color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:4px;"
|
||||
target="_blank">
|
||||
View Dashboard
|
||||
</a> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Bottom Graphic -->
|
||||
<tr>
|
||||
<td style="font-size:0px;padding:0px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#fafafa;background-color:#fafafa;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#fafafa;background-color:#fafafa;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#626262;">Service Domain</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;line-height:1;text-align:left;color:#626262;">{{.Service.Domain}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:0px;padding:0px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
{{if .Failure}}
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#626262;">Current Issue</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;line-height:1;text-align:left;color:#626262;">{{.Failure.Issue}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:0px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td height="30" style="vertical-align:top;height:30px;">
|
||||
|
||||
<![endif]-->
|
||||
<div style="height:30px;"> </div>
|
||||
<!--[if mso | IE]>
|
||||
|
||||
</td></tr></table>
|
||||
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:0px;padding:0;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
|
||||
<v:rect style="width:600px;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
|
||||
<v:fill origin="0.5, 0" position="0.5, 0" src="{{$banner}}" color="#F15822" type="tile" />
|
||||
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
|
||||
<![endif]-->
|
||||
<div style="background:#F15822 url({{$banner}}) top center / auto repeat;margin:0px auto;max-width:600px;">
|
||||
<div style="line-height:0;font-size:0;">
|
||||
<table align="center" background="{{$banner}}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#F15822 url({{$banner}}) top center / auto repeat;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:250px;"> <a href="https://www.sphero.com" target="_blank">
|
||||
|
||||
<img
|
||||
height="auto" src="https://assets.statping.com/statpingcom.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="250"
|
||||
/>
|
||||
|
||||
</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</v:textbox>
|
||||
</v:rect>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:0px;padding:20px 0;padding-top:10px;padding-bottom:0;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0;padding-top:10px;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;line-height:16px;text-align:center;color:#445566;">You are receiving this email because one of your services has changed on your Statping instance. You can modify this email on the Email Notifier page in Settings.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;line-height:16px;text-align:center;color:#445566;">© Statping</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size:0px;padding:20px 0;padding-top:0;padding-bottom:0;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0;padding-top:0;text-align:center;">
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
|
||||
<td
|
||||
class="" style="width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
>
|
||||
<tr>
|
||||
|
||||
<td
|
||||
style="vertical-align:top;width:600px;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align:top;padding-right:0;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;font-weight:bold;line-height:16px;text-align:center;color:#445566;"><a class="footer-link" href="https://statping.com">Statping.com</a>         <a class="footer-link" href="https://github.com/statping/statping">Github</a>        
|
||||
<a class="footer-link" href="https://statping.com/privacy">Privacy</a>         <a class="footer-link" href="https://www.google.com">Unsubscribe</a></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
`
|
|
@ -0,0 +1,270 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/statping/statping/utils"
|
||||
"github.com/tdewolff/minify/v2"
|
||||
"github.com/tdewolff/minify/v2/html"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mjmlApplication string
|
||||
mjmlPrivate string
|
||||
)
|
||||
|
||||
func main() {
|
||||
utils.InitEnvs()
|
||||
|
||||
mjmlApplication = os.Getenv("MJML_APP")
|
||||
mjmlPrivate = os.Getenv("MJML_PRIVATE")
|
||||
|
||||
if mjmlApplication == "" || mjmlPrivate == "" {
|
||||
fmt.Println("skipping email MJML template render, missing MJML_APP and MJML_PRIVATE")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Generating success/failure email templates from MJML to a HTML golang constant")
|
||||
|
||||
success := convertMJML(emailSuccessMJML)
|
||||
fail := convertMJML(emailFailureMJML)
|
||||
|
||||
htmlOut := `// DO NOT EDIT ** This file was generated with go generate on ` + utils.Now().String() + ` ** DO NOT EDIT //
|
||||
package notifiers
|
||||
|
||||
const emailSuccess = ` + minimize(success) + `
|
||||
|
||||
const emailFailure = ` + minimize(fail) + `
|
||||
|
||||
`
|
||||
|
||||
utils.SaveFile("email_rendered.go", []byte(htmlOut))
|
||||
|
||||
fmt.Println("Email MJML to HTML const saved: notifiers/email_rendered.go")
|
||||
}
|
||||
|
||||
type mjmlInput struct {
|
||||
Mjml string `json:"mjml"`
|
||||
}
|
||||
|
||||
func minimize(val string) string {
|
||||
m := minify.New()
|
||||
m.Add("text/html", &html.Minifier{
|
||||
KeepDefaultAttrVals: true,
|
||||
})
|
||||
s, err := m.String("text/html", val)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("`%s`", s)
|
||||
}
|
||||
|
||||
func convertMJML(mjml string) string {
|
||||
input, _ := json.Marshal(mjmlInput{mjml})
|
||||
auth := fmt.Sprintf("%s:%s", mjmlApplication, mjmlPrivate)
|
||||
resp, _, err := utils.HttpRequest("https://"+auth+"@api.mjml.io/v1/render", "POST", "application/json", nil, bytes.NewBuffer(input), 15*time.Minute, false, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var respData mjmlApi
|
||||
if err := json.Unmarshal(resp, &respData); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return respData.Html
|
||||
}
|
||||
|
||||
type mjmlApi struct {
|
||||
Html string `json:"html"`
|
||||
Mjml string `json:"mjml"`
|
||||
Version string `json:"mjml_version"`
|
||||
}
|
||||
|
||||
const emailFailureMJML = `<mjml>
|
||||
<mj-head>
|
||||
<mj-title>Statping Service Notification</mj-title>
|
||||
</mj-head>
|
||||
<mj-body background-color="#E7E7E7">
|
||||
<mj-raw>
|
||||
<!-- Top Bar -->
|
||||
</mj-raw>
|
||||
<mj-section background-color="#a30911" background-url="https://assets.statping.com/offlinebanner.png" padding="0px">
|
||||
<mj-column>
|
||||
<mj-image width="45px" href="https://statping.com" src="https://assets.statping.com/iconlight.png" align="center" alt="Sphero"></mj-image>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section background-color="#ffffff">
|
||||
<mj-column width="100%">
|
||||
<mj-text font-family="Ubuntu, Helvetica, Arial, sans-serif" font-size="22px" padding="15px" line-height="30px">
|
||||
{{.Service.Name}} is currently offline, you might want to check it.
|
||||
</mj-text>
|
||||
|
||||
|
||||
<mj-section padding-left="0px" padding-right="0px" padding-top="10px" padding-bottom="10px">
|
||||
<mj-column>
|
||||
<mj-text font-color="#d50d0d" align="center" font-size="20px" color="#626262">Offline for {{.Service.Downtime.Human}}</mj-text>
|
||||
|
||||
<mj-button border-radius="4px" background-color="#cb121c" href="{{.Core.Domain}}/service/{{.Service.Id}}">View Dashboard</mj-button>
|
||||
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
|
||||
<mj-raw>
|
||||
<!-- Bottom Graphic -->
|
||||
</mj-raw>
|
||||
|
||||
|
||||
|
||||
|
||||
<mj-section padding="0px" background-color="#fafafa">
|
||||
<mj-column>
|
||||
<mj-text font-size="20px" color="#626262">Service Domain</mj-text>
|
||||
<mj-text padding-top="0px" font-size="14px" color="#626262">{{.Service.Domain}}</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section padding="0px" background-color="#ffffff">
|
||||
<mj-column>
|
||||
<mj-text font-size="20px" color="#626262">Current Issue</mj-text>
|
||||
<mj-text padding-top="0px" font-size="14px" color="#626262">{{.Failure.Issue}}</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
|
||||
<mj-spacer height="30px" />
|
||||
|
||||
<mj-section padding="0" background-url="https://assets.statping.com/offlinebanner.png" background-color="#a30911">
|
||||
<mj-column>
|
||||
<mj-image width="250px" href="https://statping.com" src="https://assets.statping.com/statpingcom.png" align="center"></mj-image>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section padding-bottom="0" padding-top="10px">
|
||||
<mj-column>
|
||||
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
|
||||
You are receiving this email because one of your services has changed on your Statping instance. You can modify this email on the Email Notifier page in Settings.
|
||||
</mj-text>
|
||||
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
|
||||
© Statping
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section padding-top="0" padding-bottom="0">
|
||||
<mj-group>
|
||||
<mj-column width="100%" padding-right="0">
|
||||
<mj-text color="#445566" font-size="11px" align="center" line-height="16px" font-weight="bold">
|
||||
<a class="footer-link" href="https://statping.com">Statping.com</a>        
|
||||
|
||||
<a class="footer-link" href="https://github.com/statping/statping">Github</a>        
|
||||
|
||||
<a class="footer-link" href="https://statping.com/privacy">Privacy</a>        
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-group>
|
||||
|
||||
</mj-section>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
</mj-body>
|
||||
</mjml>`
|
||||
|
||||
const emailSuccessMJML = `<mjml>
|
||||
<mj-head>
|
||||
<mj-title>Statping Service Notification</mj-title>
|
||||
</mj-head>
|
||||
<mj-body background-color="#E7E7E7">
|
||||
<mj-raw>
|
||||
<!-- Top Bar -->
|
||||
</mj-raw>
|
||||
<mj-section background-color="#12ab0c" background-url="https://assets.statping.com/greenbackground.png" padding="0px">
|
||||
<mj-column>
|
||||
<mj-image width="45px" href="https://statping.com" src="https://assets.statping.com/iconlight.png" align="center" alt="Sphero"></mj-image>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section background-color="#ffffff">
|
||||
<mj-column width="100%">
|
||||
<mj-text font-family="Ubuntu, Helvetica, Arial, sans-serif" font-size="22px" padding="15px" line-height="30px">
|
||||
{{.Service.Name}} is currently offline, you might want to check it.
|
||||
</mj-text>
|
||||
|
||||
|
||||
<mj-section padding-left="0px" padding-right="0px" padding-top="10px" padding-bottom="10px">
|
||||
<mj-column>
|
||||
<mj-text font-color="#d50d0d" align="center" font-size="20px" color="#626262">Offline for {{.Service.Downtime.Human}}</mj-text>
|
||||
|
||||
<mj-button border-radius="4px" background-color="#4caf50" href="{{.Core.Domain}}/service/{{.Service.Id}}">View Dashboard</mj-button>
|
||||
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
|
||||
<mj-raw>
|
||||
<!-- Bottom Graphic -->
|
||||
</mj-raw>
|
||||
|
||||
|
||||
|
||||
|
||||
<mj-section padding="0px" background-color="#fafafa">
|
||||
<mj-column>
|
||||
<mj-text font-size="20px" color="#626262">Service Domain</mj-text>
|
||||
<mj-text padding-top="0px" font-size="14px" color="#626262">{{.Service.Domain}}</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section padding="0px" background-color="#ffffff">
|
||||
<mj-column>
|
||||
<mj-text font-size="20px" color="#626262">Current Issue</mj-text>
|
||||
<mj-text padding-top="0px" font-size="14px" color="#626262">{{.Failure.Issue}}</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
|
||||
<mj-spacer height="30px" />
|
||||
|
||||
<mj-section padding="0" background-url="https://assets.statping.com/greenbackground.png" background-color="#12ab0c">
|
||||
<mj-column>
|
||||
<mj-image width="250px" href="https://statping.com" src="https://assets.statping.com/statpingcom.png" align="center"></mj-image>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section padding-bottom="0" padding-top="10px">
|
||||
<mj-column>
|
||||
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
|
||||
You are receiving this email because one of your services has changed on your Statping instance. You can modify this email on the Email Notifier page in Settings.
|
||||
</mj-text>
|
||||
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
|
||||
© Statping
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section padding-top="0" padding-bottom="0">
|
||||
<mj-group>
|
||||
<mj-column width="100%" padding-right="0">
|
||||
<mj-text color="#445566" font-size="11px" align="center" line-height="16px" font-weight="bold">
|
||||
<a class="footer-link" href="https://statping.com">Statping.com</a>        
|
||||
|
||||
<a class="footer-link" href="https://github.com/statping/statping">Github</a>        
|
||||
|
||||
<a class="footer-link" href="https://statping.com/privacy">Privacy</a>        
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-group>
|
||||
|
||||
</mj-section>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
</mj-body>
|
||||
</mjml>`
|
|
@ -22,6 +22,10 @@ func (g *gotify) Select() *notifications.Notification {
|
|||
return g.Notification
|
||||
}
|
||||
|
||||
func (g *gotify) Valid(values notifications.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var Gotify = &gotify{¬ifications.Notification{
|
||||
Method: "gotify",
|
||||
Title: "Gotify",
|
||||
|
|
|
@ -21,9 +21,10 @@ var (
|
|||
)
|
||||
|
||||
func TestGotifyNotifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := utils.InitLogs()
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Parallel()
|
||||
GOTIFY_URL = utils.Params.GetString("GOTIFY_URL")
|
||||
GOTIFY_TOKEN = utils.Params.GetString("GOTIFY_TOKEN")
|
||||
|
||||
|
|
|
@ -26,6 +26,10 @@ func (l *lineNotifier) Select() *notifications.Notification {
|
|||
return l.Notification
|
||||
}
|
||||
|
||||
func (l *lineNotifier) Valid(values notifications.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var LineNotify = &lineNotifier{¬ifications.Notification{
|
||||
Method: lineNotifyMethod,
|
||||
Title: "LINE Notify",
|
||||
|
|
|
@ -22,6 +22,10 @@ func (m *mobilePush) Select() *notifications.Notification {
|
|||
return m.Notification
|
||||
}
|
||||
|
||||
func (m *mobilePush) Valid(values notifications.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var Mobile = &mobilePush{¬ifications.Notification{
|
||||
Method: "mobile",
|
||||
Title: "Mobile",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue