mirror of https://github.com/statping/statping
Merge branch 'master' into fix/216
commit
7f9e639898
|
@ -16,4 +16,5 @@ dev
|
|||
!dev/demo-script.sh
|
||||
!build/alpine-linux-amd64
|
||||
config.yml
|
||||
statup.db
|
||||
*.db
|
||||
tmp
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
*.gohtml linguist-language=golang
|
||||
*.js linguist-detectable=false
|
||||
*.yml linguist-detectable=false
|
||||
*.json linguist-detectable=false
|
||||
dev/* linguist-vendored
|
|
@ -0,0 +1,2 @@
|
|||
github: hunterlong
|
||||
custom: ['https://www.buymeacoffee.com/hunterlong']
|
|
@ -5,7 +5,7 @@ stage
|
|||
parts
|
||||
core/rice-box.go
|
||||
config.yml
|
||||
statup.db
|
||||
*.db
|
||||
plugins/*.so
|
||||
data
|
||||
build
|
||||
|
@ -19,6 +19,7 @@ assets
|
|||
*.log
|
||||
.env
|
||||
logs
|
||||
tmp
|
||||
/dev/test/node_modules
|
||||
dev/test/cypress/videos
|
||||
dev/test/cypress/screenshots
|
||||
|
|
83
.travis.yml
83
.travis.yml
|
@ -1,74 +1,59 @@
|
|||
os:
|
||||
- linux
|
||||
|
||||
- linux
|
||||
language: go
|
||||
go: 1.12.1
|
||||
go: 1.13.5
|
||||
go_import_path: github.com/hunterlong/statping
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/pkg/dep
|
||||
- ~/.npm
|
||||
- ~/.cache
|
||||
- $GOPATH/src/github.com/hunterlong/statping/vendor
|
||||
|
||||
- "~/.npm"
|
||||
- "~/.cache"
|
||||
- "$GOPATH/src/github.com/hunterlong/statping/tmp"
|
||||
- "$GOPATH/src/github.com/hunterlong/statping/vendor"
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
- postgresql
|
||||
- mysql
|
||||
- mongodb
|
||||
|
||||
- docker
|
||||
- postgresql
|
||||
- mysql
|
||||
- mongodb
|
||||
env:
|
||||
global:
|
||||
- PATH=/snap/bin:$PATH
|
||||
- DB_HOST=localhost
|
||||
- DB_USER=travis
|
||||
- DB_PASS=
|
||||
- DB_DATABASE=test
|
||||
- GO_ENV=test
|
||||
- STATPING_DIR=$GOPATH/src/github.com/hunterlong/statping
|
||||
|
||||
- PATH=$HOME/.local/bin:$PATH
|
||||
- DB_HOST=localhost
|
||||
- DB_USER=travis
|
||||
- DB_PASS=
|
||||
- DB_DATABASE=test
|
||||
- GO_ENV=test
|
||||
- STATPING_DIR=$GOPATH/src/github.com/hunterlong/statping
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
- go: master
|
||||
fast_finish: true
|
||||
|
||||
notifications:
|
||||
email: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
||||
|
||||
before_install:
|
||||
- curl -L -s https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 -o $GOPATH/bin/dep
|
||||
- chmod +x $GOPATH/bin/dep
|
||||
|
||||
- master
|
||||
install:
|
||||
- npm install -g sass
|
||||
- npm install -g newman
|
||||
- make dev-deps
|
||||
- make dep
|
||||
- make install
|
||||
|
||||
- npm install -g sass
|
||||
- npm install -g newman
|
||||
- pip install --user awscli
|
||||
- go mod vendor
|
||||
- make dev-deps
|
||||
- make install
|
||||
before_script:
|
||||
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
|
||||
- psql -c 'create database test;' -U postgres
|
||||
|
||||
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
|
||||
- psql -c 'create database test;' -U postgres
|
||||
script:
|
||||
- travis_retry make test-all
|
||||
- make test-api
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" = "false" ]]; then make coverage; fi
|
||||
|
||||
- travis_retry make test-all
|
||||
- make test-api
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" = "false" ]]; then
|
||||
make coverage; fi
|
||||
after_success:
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" = "false" ]]; then make travis-build; fi
|
||||
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" = "false" ]]; then
|
||||
make travis-build; fi
|
||||
webhooks:
|
||||
urls:
|
||||
- $GITTER
|
||||
- "$GITTER"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_start: never
|
||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -1,15 +1,12 @@
|
|||
FROM golang:1.12-alpine as base
|
||||
FROM golang:1.13.5-alpine as base
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
ARG VERSION
|
||||
ENV DEP_VERSION v0.5.0
|
||||
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass
|
||||
RUN curl -L -s https://github.com/golang/dep/releases/download/$DEP_VERSION/dep-linux-amd64 -o /go/bin/dep && \
|
||||
chmod +x /go/bin/dep
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
WORKDIR /go/src/github.com/hunterlong/statping
|
||||
ADD Makefile Gopkg.* /go/src/github.com/hunterlong/statping/
|
||||
RUN make dep && \
|
||||
ADD Makefile go.mod /go/src/github.com/hunterlong/statping/
|
||||
RUN go mod vendor && \
|
||||
make dev-deps
|
||||
ADD . /go/src/github.com/hunterlong/statping
|
||||
RUN make install
|
||||
|
@ -31,6 +28,6 @@ WORKDIR /app
|
|||
VOLUME /app
|
||||
EXPOSE $PORT
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=5s --retries=5 CMD curl -s "http://localhost:$PORT/health" | jq -r -e ".online==true"
|
||||
HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD curl -s "http://localhost:$PORT/health" | jq -r -e ".online==true"
|
||||
|
||||
CMD statping -port $PORT
|
||||
|
|
|
@ -1,328 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a2de8c02990ea7cb28b962d2effa6840cc2c849b298736eef57a9713432cc0d3"
|
||||
name = "github.com/99designs/gqlgen"
|
||||
packages = [
|
||||
"complexity",
|
||||
"graphql",
|
||||
"graphql/introspection",
|
||||
"handler",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "afe33f73875beca92e917742c1f49c1f6145018b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:07f7314344b2771963ada0b2a4a426c59d782dac227dcfff2499188a186446c0"
|
||||
name = "github.com/GeertJohan/go.rice"
|
||||
packages = [
|
||||
".",
|
||||
"embedded",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "cd53cd147dd5288bc2fb990fb983e58e301abb5e"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:786e862ec180708b60ee670723e3edd969fd4309e7b1c315cd7de058ac62a011"
|
||||
name = "github.com/agnivade/levenshtein"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "51b298ff305e72cfd29166dccc3f9878e82f9fdc"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f1ec92a2b8473612547f6e13edbc8c8e6cda6c8be9c54b31958aad4a7ccaaa2b"
|
||||
name = "github.com/ararog/timeago"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "518814407569bf983ea81e1bf8b550dd4e7b34f3"
|
||||
version = "0.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:86b9f9c54fcddec313009ef19073bf0124bbdae3701080c32f67c0c8817b3b7c"
|
||||
name = "github.com/daaku/go.zipexe"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "db7cf2ba330f8c2d28b827826e33d6628ea7e9e0"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3806f369b846160fcbde19bdcf93790868defe7c58d1bb6bc8d974c5b8f8dc1e"
|
||||
name = "github.com/go-mail/mail"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f59b9b83a4e522098e3d3eb94e6f81850ad6e973"
|
||||
version = "v2.3.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65"
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "72cd26f257d44c1114970e19afddcd812016007e"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
|
||||
name = "github.com/go-yaml/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
||||
version = "v2.2.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d5f97fc268267ec1b61c3453058c738246fc3e746f14b1ae25161513b7367b0c"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c5c6c98bc25355028a63748a498942a6398ccd22"
|
||||
version = "v1.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e72d1ebb8d395cf9f346fd9cbc652e5ae222dd85e0ac842dc57f175abed6d195"
|
||||
name = "github.com/gorilla/securecookie"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e5bf52fd66a2e984b57b4c0f2c4ee024ed749a19886246240629998dc0cf31ce"
|
||||
name = "github.com/gorilla/sessions"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f57b7e2d29c6211d16ffa52a0998272f75799030"
|
||||
version = "v1.1.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d15ee511aa0f56baacc1eb4c6b922fa1c03b38413b6be18166b996d82a0156ea"
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = [
|
||||
".",
|
||||
"simplelru",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "7087cb70de9f7a8bc0a10c375cb0d2280a8edf9c"
|
||||
version = "v0.5.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0731b955911f880c75409845b54203f5127c72e003691e2d34462a0516a9b1f6"
|
||||
name = "github.com/jinzhu/gorm"
|
||||
packages = [
|
||||
".",
|
||||
"dialects/mysql",
|
||||
"dialects/postgres",
|
||||
"dialects/sqlite",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "e3987fd4b803c16497aa4dfd2e75db7a6a061a4e"
|
||||
version = "v1.9.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fd97437fbb6b7dce04132cf06775bd258cce305c44add58eb55ca86c6c325160"
|
||||
name = "github.com/jinzhu/inflection"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "04140366298a54a039076d798123ffa108fff46c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b"
|
||||
name = "github.com/joho/godotenv"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "23d116af351c84513e1946b527c88823e476be13"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:226be3582c304c347481157049c862924fdd6277256e854781c5ba4728901215"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"hstore",
|
||||
"oid",
|
||||
"scram",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "51e2106eed1cea199c802d2a49e91e2491b02056"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4a49346ca45376a2bba679ca0e83bec949d780d4e927931317904bad482943ec"
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c7c4067b79cc51e6dfdcef5c702e74b1e0fa7c75"
|
||||
version = "v1.10.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8dd6663207b795abbe94a20d2785c9eb16be59183f5468e8816f98aeda466c7f"
|
||||
name = "github.com/rendon/testcli"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "6283090d169f51a2410b4e260341a01c9a4c0ca7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9421f6e9e28ef86933e824b5caff441366f2b69bb281085b9dca40e1f27a1602"
|
||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "UT"
|
||||
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9f3a60def1a1eb5ac184e71dde43c6f99606f54d106db7c95f8b8338629a777b"
|
||||
name = "github.com/tatsushid/go-fastping"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d7bb493dee3e090e2ffb6914adddf17c1e7c026c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b4e8aaca88f799355f4ac560bce4293fb85ff21003dd0d5741ca503f7a788e91"
|
||||
name = "github.com/vektah/gqlparser"
|
||||
packages = [
|
||||
".",
|
||||
"ast",
|
||||
"gqlerror",
|
||||
"lexer",
|
||||
"parser",
|
||||
"validator",
|
||||
"validator/rules",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "05741cdb0871330d8bc980d4afd21ab34eceee83"
|
||||
version = "v1.1.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9d5b5d543996dd584da1db1e0de1926f3e4c3a8dba0fa2f8db70f3ebee2342e0"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a29dc8fdc73485234dbef99ebedb95d2eced08de"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5e22b2014e7cd102e2b41ab7dd38b9c5175e3447b653033987164bebc858e958"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"bpf",
|
||||
"icmp",
|
||||
"internal/iana",
|
||||
"internal/socket",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4829fb13d2c62012c17688fa7f629f371014946d"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["cloudsql"]
|
||||
pruneopts = "UT"
|
||||
revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
digest = "1:7388652e2215a3f45d341d58766ed58317971030eb1cbd75f005f96ace8e9196"
|
||||
name = "gopkg.in/alexcesaro/quotedprintable.v3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2caba252f4dc53eaf6b553000885530023f54623"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c805e517269b0ba4c21ded5836019ed7d16953d4026cb7d00041d039c7906be9"
|
||||
name = "gopkg.in/natefinch/lumberjack.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a96e63847dc3c67d17befa69c303767e2f84e54f"
|
||||
version = "v2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2ee0f15eb0fb04f918db7c2dcf39745f40d69f798ef171610a730e8a56aaa4fd"
|
||||
name = "gopkg.in/russross/blackfriday.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135"
|
||||
version = "v2.0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/99designs/gqlgen/graphql",
|
||||
"github.com/99designs/gqlgen/graphql/introspection",
|
||||
"github.com/99designs/gqlgen/handler",
|
||||
"github.com/GeertJohan/go.rice",
|
||||
"github.com/GeertJohan/go.rice/embedded",
|
||||
"github.com/ararog/timeago",
|
||||
"github.com/go-mail/mail",
|
||||
"github.com/go-yaml/yaml",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/gorilla/sessions",
|
||||
"github.com/jinzhu/gorm",
|
||||
"github.com/jinzhu/gorm/dialects/mysql",
|
||||
"github.com/jinzhu/gorm/dialects/postgres",
|
||||
"github.com/jinzhu/gorm/dialects/sqlite",
|
||||
"github.com/joho/godotenv",
|
||||
"github.com/rendon/testcli",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/tatsushid/go-fastping",
|
||||
"github.com/vektah/gqlparser",
|
||||
"github.com/vektah/gqlparser/ast",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
"gopkg.in/natefinch/lumberjack.v2",
|
||||
"gopkg.in/russross/blackfriday.v2",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
94
Gopkg.toml
94
Gopkg.toml
|
@ -1,94 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/99designs/gqlgen"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/GeertJohan/go.rice"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ararog/timeago"
|
||||
version = "0.0.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-mail/mail"
|
||||
version = "2.3.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-yaml/yaml"
|
||||
version = "2.2.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.7.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/sessions"
|
||||
version = "1.1.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/jinzhu/gorm"
|
||||
version = "1.9.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/joho/godotenv"
|
||||
version = "1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/rendon/testcli"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/tatsushid/go-fastping"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/vektah/gqlparser"
|
||||
version = "1.1.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/natefinch/lumberjack.v2"
|
||||
version = "2.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/russross/blackfriday.v2"
|
||||
version = "2.0.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
128
Makefile
128
Makefile
|
@ -1,24 +1,21 @@
|
|||
VERSION=$(shell cat version.txt)
|
||||
SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278
|
||||
BINARY_NAME=statping
|
||||
GOPATH:=$(GOPATH)
|
||||
GOCMD=go
|
||||
GOBUILD=$(GOCMD) build -a
|
||||
GOTEST=$(GOCMD) test
|
||||
GOGET=$(GOCMD) get
|
||||
GOVERSION=1.12.x
|
||||
GOINSTALL=$(GOCMD) install
|
||||
XGO=GOPATH=$(GOPATH) xgo -go $(GOVERSION) --dest=build
|
||||
GOBUILD=go build -a
|
||||
GOVERSION=1.13.5
|
||||
XGO=xgo -go $(GOVERSION) --dest=build
|
||||
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)"
|
||||
RICE=$(GOPATH)/bin/rice
|
||||
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
|
||||
TRVIS_SECRET=lRqWSt5BoekFK6+padJF+b77YkGdispPXEUKNuD7/Hxb7yJMoI8T/n8xZrTHtCZPdjtpy7wIlJCezNoYEZB3l2GnD6Y1QEZEXF7MIxP7hwsB/uSc5/lgdGW0ZLvTBfv6lwI/GjQIklPBW/4xcKJtj4s1YBP7xvqyIb/lDN7TiOqAKF4gqRVVfsxvlkm7j4TiPCXtz17hYQfU8kKBbd+vd3PuZgdWqs//5RwKk3Ld8QR8zoo9xXQVC5NthiyVbHznzczBsHy2cRZZoWxyi7eJM1HrDw8Jn/ivJONIHNv3RgFVn2rAoKu1X8F6FyuvPO0D2hWC62mdO/e0kt4X0mn9/6xlLSKwrHir67UgNVQe3tvlH0xNKh+yNZqR5x9t0V54vNks6Pgbhas5EfLHoWn5cF4kbJzqkXeHjt1msrsqpA3HKbmtwwjJr4Slotfiu22mAhqLSOV+xWV+IxrcNnrEq/Pa+JAzU12Uyxs8swaLJGPRAlWnJwzL9HK5aOpN0sGTuSEsTwj0WxeMMRx25YEq3+LZOgwOy3fvezmeDnKuBZa6MVCoMMpx1CRxMqAOlTGZXHjj+ZPmqDUUBpzAsFSzIdVRgcnDlLy7YRiz3tVWa1G5S07l/VcBN7ZgvCwOWZ0QgOH0MxkoDfhrfoMhNO6MBFDTRKCEl4TroPEhcInmXU8=
|
||||
PUBLISH_BODY='{ "request": { "branch": "master", "message": "Homebrew update version v${VERSION}", "config": { "env": { "VERSION": "${VERSION}", "COMMIT": "$(TRAVIS_COMMIT)" } } } }'
|
||||
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "os": [ "linux" ], "language": "go", "go": [ "${GOVERSION}" ], "go_import_path": "github.com/hunterlong/statping", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "${VERSION}" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "before_deploy": [ "git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file_glob": true, "file": "build/*", "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "wget -O statping.gpg $(SIGN_URL)", "gpg --import statping.gpg", "travis_wait 30 docker pull karalabe/xgo-latest", "make release" ], "after_success": [], "after_deploy": [ "make publish-homebrew" ] } } }'
|
||||
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statping v${VERSION}", "config": { "os": [ "linux" ], "language": "go", "go": [ "${GOVERSION}" ], "go_import_path": "github.com/hunterlong/statping", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "${VERSION}", "secure": "${TRVIS_SECRET}" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "before_deploy": [ "git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "git tag v$(VERSION) --force"], "deploy": [ { "provider": "releases", "api_key": "$$TAG_TOKEN", "file_glob": true, "file": "build/*", "skip_cleanup": true, "on": {"branch": "master"} } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull crazymax/xgo:$(GOVERSION)", "make release" ], "after_success": [], "after_deploy": [ "make publish-homebrew" ] } } }'
|
||||
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statping
|
||||
PATH:=$(PATH)
|
||||
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
|
||||
|
||||
# build all arch's and release Statping
|
||||
release: dev-deps build-all
|
||||
release: dev-deps
|
||||
wget -O statping.gpg $(SIGN_URL)
|
||||
gpg --import statping.gpg
|
||||
make build-all
|
||||
|
||||
# build and push the images to docker hub
|
||||
docker: docker-build-all docker-publish-all
|
||||
|
@ -45,6 +42,7 @@ snapcraft: clean snapcraft-build snapcraft-release
|
|||
|
||||
# build Statping for local arch
|
||||
build: compile
|
||||
go mod vendor
|
||||
$(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd
|
||||
|
||||
# build Statping plugins
|
||||
|
@ -70,10 +68,28 @@ install: build
|
|||
run: build
|
||||
./$(BINARY_NAME) --ip 0.0.0.0 --port 8080
|
||||
|
||||
# run Statping with Delve for debugging
|
||||
rundlv:
|
||||
lsof -ti:8080 | xargs kill
|
||||
DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none GO_ENV=test \
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./statping
|
||||
|
||||
killdlv:
|
||||
lsof -ti:2345 | xargs kill
|
||||
|
||||
builddlv:
|
||||
$(GOBUILD) -gcflags "all=-N -l" -o ./$(BINARY_NAME) -v ./cmd
|
||||
|
||||
watch:
|
||||
find . -print | grep -i '.*\.\(go\|gohtml\)' | justrun -v -c \
|
||||
'go build -v -gcflags "all=-N -l" -o statping ./cmd && make rundlv &' \
|
||||
-delay 10s -stdin \
|
||||
-i="Makefile,statping,statup.db,statup.db-journal,handlers/graphql/generated.go"
|
||||
|
||||
# compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go
|
||||
compile: generate
|
||||
sass source/scss/base.scss source/css/base.css
|
||||
cd source && $(GOPATH)/bin/rice embed-go
|
||||
cd source && rice embed-go
|
||||
rm -rf .sass-cache
|
||||
|
||||
# benchmark testing
|
||||
|
@ -204,52 +220,41 @@ databases:
|
|||
docker run --name statping_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password123 -e MYSQL_DATABASE=root -d mysql
|
||||
sleep 30
|
||||
|
||||
|
||||
#
|
||||
# Download and Install dependencies
|
||||
#
|
||||
# run dep to install all required golang dependecies
|
||||
dep:
|
||||
dep ensure -vendor-only
|
||||
|
||||
# install all required golang dependecies
|
||||
dev-deps:
|
||||
$(GOGET) github.com/stretchr/testify/assert
|
||||
$(GOGET) golang.org/x/tools/cmd/cover
|
||||
$(GOGET) github.com/mattn/goveralls
|
||||
$(GOINSTALL) github.com/mattn/goveralls
|
||||
$(GOGET) github.com/rendon/testcli
|
||||
$(GOGET) github.com/robertkrimen/godocdown/godocdown
|
||||
$(GOGET) github.com/karalabe/xgo
|
||||
$(GOGET) github.com/GeertJohan/go.rice
|
||||
$(GOGET) github.com/GeertJohan/go.rice/rice
|
||||
$(GOINSTALL) github.com/GeertJohan/go.rice/rice
|
||||
$(GOCMD) get github.com/axw/gocov/gocov
|
||||
$(GOCMD) get gopkg.in/matm/v1/gocov-html
|
||||
$(GOCMD) install gopkg.in/matm/v1/gocov-html
|
||||
$(GOCMD) get github.com/mgechev/revive
|
||||
$(GOCMD) get github.com/fatih/structs
|
||||
$(GOGET) github.com/ararog/timeago
|
||||
$(GOGET) gopkg.in/natefinch/lumberjack.v2
|
||||
$(GOGET) golang.org/x/crypto/bcrypt
|
||||
$(GOGET) github.com/99designs/gqlgen
|
||||
go get github.com/stretchr/testify/assert
|
||||
go get golang.org/x/tools/cmd/cover
|
||||
go get github.com/mattn/goveralls
|
||||
go install github.com/mattn/goveralls
|
||||
go get github.com/rendon/testcli
|
||||
go get github.com/robertkrimen/godocdown/godocdown
|
||||
go get github.com/crazy-max/xgo
|
||||
go get github.com/GeertJohan/go.rice
|
||||
go get github.com/GeertJohan/go.rice/rice
|
||||
go install github.com/GeertJohan/go.rice/rice
|
||||
go get github.com/axw/gocov/gocov
|
||||
go get github.com/matm/gocov-html
|
||||
go get github.com/fatih/structs
|
||||
go get github.com/ararog/timeago
|
||||
go get gopkg.in/natefinch/lumberjack.v2
|
||||
go get golang.org/x/crypto/bcrypt
|
||||
|
||||
# remove files for a clean compile/build
|
||||
clean:
|
||||
rm -rf ./{logs,assets,plugins,statup.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,statup.db,index.html,vendor}
|
||||
rm -rf cmd/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log,*.html,*.json}
|
||||
rm -rf core/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf handlers/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf notifiers/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf source/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf types/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf utils/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf dev/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log,test/app,plugin/*.so}
|
||||
rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor}
|
||||
rm -rf cmd/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,*.html,*.json}
|
||||
rm -rf core/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf handlers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf notifiers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf source/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf types/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf utils/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
|
||||
rm -rf dev/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,test/app,plugin/*.so}
|
||||
rm -rf {parts,prime,snap,stage}
|
||||
rm -rf dev/test/cypress/videos
|
||||
rm -f coverage.* sass
|
||||
rm -f source/rice-box.go
|
||||
rm -f *.db-journal
|
||||
rm -rf **/*.db-journal
|
||||
rm -rf *.snap
|
||||
find . -name "*.out" -type f -delete
|
||||
find . -name "*.cpu" -type f -delete
|
||||
|
@ -310,8 +315,19 @@ cypress-install:
|
|||
cypress-test: clean cypress-install
|
||||
cd dev/test && npm test
|
||||
|
||||
upload_to_s3:
|
||||
aws s3 cp ./source/css $(ASSETS_BKT) --recursive --exclude "*" --include "*.css"
|
||||
aws s3 cp ./source/js $(ASSETS_BKT) --recursive --exclude "*" --include "*.js"
|
||||
aws s3 cp ./source/font $(ASSETS_BKT) --recursive --exclude "*" --include "*.eot" --include "*.svg" --include "*.woff" --include "*.woff2" --include "*.ttf" --include "*.css"
|
||||
aws s3 cp ./source/scss $(ASSETS_BKT) --recursive --exclude "*" --include "*.scss"
|
||||
aws s3 cp ./install.sh $(ASSETS_BKT)
|
||||
|
||||
travis_s3_creds:
|
||||
mkdir -p ~/.aws
|
||||
echo "[default]\naws_access_key_id = ${AWS_ACCESS_KEY_ID}\naws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}" > ~/.aws/credentials
|
||||
|
||||
# build Statping using a travis ci trigger
|
||||
travis-build:
|
||||
travis-build: travis_s3_creds upload_to_s3
|
||||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(TRAVIS_BUILD_CMD) https://api.travis-ci.com/repo/hunterlong%2Fstatping/requests
|
||||
curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER)
|
||||
|
||||
|
@ -344,12 +360,16 @@ valid-sign:
|
|||
|
||||
# install xgo and pull the xgo docker image
|
||||
xgo-install: clean
|
||||
go get github.com/karalabe/xgo
|
||||
docker pull karalabe/xgo-latest
|
||||
go get github.com/crazy-max/xgo
|
||||
docker pull crazy-max/xgo:${GOVERSION}
|
||||
|
||||
heroku:
|
||||
git push heroku master
|
||||
heroku container:push web
|
||||
heroku container:release web
|
||||
|
||||
checkall:
|
||||
golangci-lint run ./...
|
||||
|
||||
.PHONY: all build build-all build-alpine test-all test test-api docker
|
||||
.SILENT: travis_s3_creds
|
||||
|
|
121
cmd/cli.go
121
cmd/cli.go
|
@ -34,43 +34,43 @@ import (
|
|||
// catchCLI will run functions based on the commands sent to Statping
|
||||
func catchCLI(args []string) error {
|
||||
dir := utils.Directory
|
||||
if err := utils.InitLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
source.Assets()
|
||||
runLogs := utils.InitLogs
|
||||
runAssets := source.Assets
|
||||
loadDotEnvs()
|
||||
|
||||
switch args[0] {
|
||||
case "version":
|
||||
if COMMIT != "" {
|
||||
fmt.Printf("Statping v%v (%v)\n", VERSION, COMMIT)
|
||||
fmt.Printf("%v (%v)\n", VERSION, COMMIT)
|
||||
} else {
|
||||
fmt.Printf("Statping v%v\n", VERSION)
|
||||
fmt.Printf("%v\n", VERSION)
|
||||
}
|
||||
return errors.New("end")
|
||||
case "assets":
|
||||
var err error
|
||||
if err = runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = source.CreateAllAssets(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("end")
|
||||
case "sass":
|
||||
if err := runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := source.CompileSASS(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("end")
|
||||
case "update":
|
||||
var err error
|
||||
var gitCurrent githubResponse
|
||||
if gitCurrent, err = checkGithubUpdates(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Statping Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName)
|
||||
if VERSION != gitCurrent.TagName[1:] {
|
||||
fmt.Printf("You don't have the latest version v%v!\nDownload the latest release at: https://github.com/hunterlong/statping\n", gitCurrent.TagName[1:])
|
||||
} else {
|
||||
fmt.Printf("You have the latest version of Statping!\n")
|
||||
}
|
||||
updateDisplay()
|
||||
return errors.New("end")
|
||||
case "test":
|
||||
cmd := args[1]
|
||||
|
@ -81,32 +81,40 @@ func catchCLI(args []string) error {
|
|||
return errors.New("end")
|
||||
case "static":
|
||||
var err error
|
||||
if err = runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION)
|
||||
utils.InitLogs()
|
||||
if core.Configs, err = core.LoadConfigFile(dir); err != nil {
|
||||
utils.Log(4, "config.yml file not found")
|
||||
if core.CoreApp.Config, err = core.LoadConfigFile(dir); err != nil {
|
||||
log.Errorln("config.yml file not found")
|
||||
return err
|
||||
}
|
||||
indexSource := ExportIndexHTML()
|
||||
//core.CloseDB()
|
||||
if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
utils.Log(1, "Exported Statping index page: 'index.html'")
|
||||
log.Infoln("Exported Statping index page: 'index.html'")
|
||||
case "help":
|
||||
HelpEcho()
|
||||
return errors.New("end")
|
||||
case "export":
|
||||
var err error
|
||||
var data []byte
|
||||
if err := utils.InitLogs(); err != nil {
|
||||
if err = runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if core.Configs, err = core.LoadConfigFile(dir); err != nil {
|
||||
if err = runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = core.Configs.Connect(false, dir); err != nil {
|
||||
if core.CoreApp.Config, err = core.LoadConfigFile(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = core.CoreApp.Connect(false, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if data, err = core.ExportSettings(); err != nil {
|
||||
|
@ -133,16 +141,28 @@ func catchCLI(args []string) error {
|
|||
}
|
||||
return errors.New("end")
|
||||
case "run":
|
||||
utils.Log(1, "Running 1 time and saving to database...")
|
||||
RunOnce()
|
||||
if err := runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("Running 1 time and saving to database...")
|
||||
runOnce()
|
||||
//core.CloseDB()
|
||||
fmt.Println("Check is complete.")
|
||||
return errors.New("end")
|
||||
case "env":
|
||||
fmt.Println("Statping Environment Variable")
|
||||
if err := runLogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runAssets(); err != nil {
|
||||
return err
|
||||
}
|
||||
envs, err := godotenv.Read(".env")
|
||||
if err != nil {
|
||||
utils.Log(4, "No .env file found in current directory.")
|
||||
log.Errorln("No .env file found in current directory.")
|
||||
return err
|
||||
}
|
||||
for k, e := range envs {
|
||||
|
@ -157,7 +177,7 @@ func catchCLI(args []string) error {
|
|||
// ExportIndexHTML returns the HTML of the index page as a string
|
||||
func ExportIndexHTML() []byte {
|
||||
source.Assets()
|
||||
core.Configs.Connect(false, utils.Directory)
|
||||
core.CoreApp.Connect(false, utils.Directory)
|
||||
core.CoreApp.SelectAllServices(false)
|
||||
core.CoreApp.UseCdn = types.NewNullBool(true)
|
||||
for _, srv := range core.CoreApp.Services {
|
||||
|
@ -170,16 +190,31 @@ func ExportIndexHTML() []byte {
|
|||
return w.Body.Bytes()
|
||||
}
|
||||
|
||||
// RunOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
|
||||
func RunOnce() {
|
||||
func updateDisplay() error {
|
||||
var err error
|
||||
core.Configs, err = core.LoadConfigFile(utils.Directory)
|
||||
if err != nil {
|
||||
utils.Log(4, "config.yml file not found")
|
||||
var gitCurrent githubResponse
|
||||
if gitCurrent, err = checkGithubUpdates(); err != nil {
|
||||
fmt.Printf("Issue connecting to https://github.com/hunterlong/statping\n%v\n", err)
|
||||
return err
|
||||
}
|
||||
err = core.Configs.Connect(false, utils.Directory)
|
||||
if VERSION != gitCurrent.TagName[1:] {
|
||||
fmt.Printf("\nNew Update %v Available!\n", gitCurrent.TagName[1:])
|
||||
fmt.Printf("Update Command:\n")
|
||||
fmt.Printf("curl -o- -L https://statping.com/install.sh | bash\n\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
|
||||
func runOnce() {
|
||||
var err error
|
||||
core.CoreApp.Config, err = core.LoadConfigFile(utils.Directory)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln("config.yml file not found")
|
||||
}
|
||||
err = core.CoreApp.Connect(false, utils.Directory)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
core.CoreApp, err = core.SelectCore()
|
||||
if err != nil {
|
||||
|
@ -187,7 +222,7 @@ func RunOnce() {
|
|||
}
|
||||
_, err = core.CoreApp.SelectAllServices(true)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
for _, out := range core.CoreApp.Services {
|
||||
out.Check(true)
|
||||
|
@ -214,9 +249,12 @@ func HelpEcho() {
|
|||
fmt.Printf("Flags:\n")
|
||||
fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)")
|
||||
fmt.Println(" -port 8080 - Run HTTP server on Port (default: 8080)")
|
||||
fmt.Println(" -verbose 1 - Verbose mode levels 1 - 4 (default: 1)")
|
||||
fmt.Println(" -env path/debug.env - Optional .env file to set as environment variables while running server")
|
||||
fmt.Printf("Environment Variables:\n")
|
||||
fmt.Println(" PORT - Set the outgoing port for the HTTP server (or use -port)")
|
||||
fmt.Println(" IP - Bind a specific IP address to the HTTP server (or use -ip)")
|
||||
fmt.Println(" VERBOSE - Display more logs in verbose mode. (1 - 4)")
|
||||
fmt.Println(" STATPING_DIR - Set a absolute path for the root path of Statping server (logs, assets, SQL db)")
|
||||
fmt.Println(" DISABLE_LOGS - Disable viewing and writing to the log file (default is false)")
|
||||
fmt.Println(" DB_CONN - Automatic Database connection (sqlite, postgres, mysql)")
|
||||
|
@ -226,7 +264,7 @@ func HelpEcho() {
|
|||
fmt.Println(" DB_PORT - Database port (5432, 3306, ...)")
|
||||
fmt.Println(" DB_DATABASE - Database connection's database name")
|
||||
fmt.Println(" POSTGRES_SSLMODE - Enable Postgres SSL Mode 'ssl_mode=VALUE' (enable/disable/verify-full/verify-ca)")
|
||||
fmt.Println(" GO_ENV - Run Statping in testmode, will bypass HTTP authentication (if set as 'true')")
|
||||
fmt.Println(" GO_ENV - Run Statping in testmode, will bypass HTTP authentication (if set as 'test')")
|
||||
fmt.Println(" NAME - Set a name for the Statping status page")
|
||||
fmt.Println(" DESCRIPTION - Set a description for the Statping status page")
|
||||
fmt.Println(" DOMAIN - Set a URL for the Statping status page")
|
||||
|
@ -234,17 +272,20 @@ func HelpEcho() {
|
|||
fmt.Println(" ADMIN_PASS - Password for administrator account (default: admin)")
|
||||
fmt.Println(" SASS - Set the absolute path to the sass binary location")
|
||||
fmt.Println(" HTTP_PROXY - Use a HTTP Proxy for HTTP Requests")
|
||||
fmt.Println(" AUTH_USERNAME - HTTP Basic Authentication username")
|
||||
fmt.Println(" AUTH_PASSWORD - HTTP Basic Authentication password")
|
||||
fmt.Println(" BASE_PATH - Set the base URL prefix (set to 'monitor' if URL is domain.com/monitor)")
|
||||
fmt.Println(" * You can insert environment variables into a '.env' file in root directory.")
|
||||
fmt.Println("Give Statping a Star at https://github.com/hunterlong/statping")
|
||||
}
|
||||
|
||||
func checkGithubUpdates() (githubResponse, error) {
|
||||
var gitResp githubResponse
|
||||
url := "https://api.github.com/repos/hunterlong/statping/releases/latest"
|
||||
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(10*time.Second))
|
||||
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true)
|
||||
if err != nil {
|
||||
return githubResponse{}, err
|
||||
}
|
||||
var gitResp githubResponse
|
||||
err = json.Unmarshal(contents, &gitResp)
|
||||
return gitResp, err
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/rendon/testcli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -52,7 +51,7 @@ func TestStartServerCommand(t *testing.T) {
|
|||
func TestVersionCommand(t *testing.T) {
|
||||
c := testcli.Command("statping", "version")
|
||||
c.Run()
|
||||
assert.True(t, c.StdoutContains("Statping v"+VERSION))
|
||||
assert.True(t, c.StdoutContains(VERSION))
|
||||
}
|
||||
|
||||
func TestHelpCommand(t *testing.T) {
|
||||
|
@ -90,7 +89,7 @@ func TestUpdateCommand(t *testing.T) {
|
|||
commandAndSleep(cmd, time.Duration(15*time.Second), got)
|
||||
gg, _ := <-got
|
||||
t.Log(gg)
|
||||
assert.Contains(t, gg, "Statping")
|
||||
assert.Contains(t, gg, VERSION)
|
||||
}
|
||||
|
||||
func TestAssetsCommand(t *testing.T) {
|
||||
|
@ -98,6 +97,7 @@ func TestAssetsCommand(t *testing.T) {
|
|||
c.Run()
|
||||
t.Log(c.Stdout())
|
||||
t.Log("Directory for Assets: ", dir)
|
||||
time.Sleep(1 * time.Second)
|
||||
assert.FileExists(t, dir+"/assets/robots.txt")
|
||||
assert.FileExists(t, dir+"/assets/scss/base.scss")
|
||||
}
|
||||
|
@ -166,7 +166,6 @@ func TestRunOnceCLI(t *testing.T) {
|
|||
func TestEnvCLI(t *testing.T) {
|
||||
run := catchCLI([]string{"env"})
|
||||
assert.Error(t, run)
|
||||
Clean()
|
||||
}
|
||||
|
||||
func commandAndSleep(cmd *exec.Cmd, duration time.Duration, out chan<- string) {
|
||||
|
@ -186,15 +185,3 @@ func runCommand(c *exec.Cmd, out chan<- string) {
|
|||
bout, _ := c.CombinedOutput()
|
||||
out <- string(bout)
|
||||
}
|
||||
|
||||
func Clean() {
|
||||
utils.DeleteFile(dir + "/config.yml")
|
||||
utils.DeleteFile(dir + "/statping.db")
|
||||
utils.DeleteDirectory(dir + "/assets")
|
||||
utils.DeleteDirectory(dir + "/logs")
|
||||
core.CoreApp = core.NewCore()
|
||||
source.Assets()
|
||||
//core.CloseDB()
|
||||
os.Unsetenv("DB_CONN")
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
|
94
cmd/main.go
94
cmd/main.go
|
@ -16,16 +16,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/utils"
|
||||
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/handlers"
|
||||
_ "github.com/hunterlong/statping/notifiers"
|
||||
"github.com/hunterlong/statping/plugin"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/joho/godotenv"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -34,8 +36,10 @@ var (
|
|||
// COMMIT stores the git commit hash for this version of Statping
|
||||
COMMIT string
|
||||
ipAddress string
|
||||
UsingDotEnv bool
|
||||
envFile string
|
||||
verboseMode int
|
||||
port int
|
||||
log = utils.Log.WithField("type", "cmd")
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -46,28 +50,33 @@ func init() {
|
|||
// -ip = 0.0.0.0 IP address for outgoing HTTP server
|
||||
// -port = 8080 Port number for outgoing HTTP server
|
||||
func parseFlags() {
|
||||
ip := flag.String("ip", "0.0.0.0", "IP address to run the Statping HTTP server")
|
||||
p := flag.Int("port", 8080, "Port to run the HTTP server")
|
||||
flag.StringVar(&ipAddress, "ip", "0.0.0.0", "IP address to run the Statping HTTP server")
|
||||
flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server")
|
||||
flag.IntVar(&port, "port", 8080, "Port to run the HTTP server")
|
||||
flag.IntVar(&verboseMode, "verbose", 2, "Run in verbose mode to see detailed logs (1 - 4)")
|
||||
flag.Parse()
|
||||
ipAddress = *ip
|
||||
port = *p
|
||||
|
||||
if os.Getenv("PORT") != "" {
|
||||
port = int(utils.ToInt(os.Getenv("PORT")))
|
||||
}
|
||||
if os.Getenv("IP") != "" {
|
||||
ipAddress = os.Getenv("IP")
|
||||
}
|
||||
if os.Getenv("VERBOSE") != "" {
|
||||
verboseMode = int(utils.ToInt(os.Getenv("VERBOSE")))
|
||||
}
|
||||
}
|
||||
|
||||
// main will run the Statping application
|
||||
func main() {
|
||||
var err error
|
||||
go sigterm()
|
||||
parseFlags()
|
||||
loadDotEnvs()
|
||||
source.Assets()
|
||||
utils.VerboseMode = verboseMode
|
||||
if err := utils.InitLogs(); err != nil {
|
||||
fmt.Printf("Statping Log Error: \n %v\n", err)
|
||||
os.Exit(2)
|
||||
log.Errorf("Statping Log Error: %v\n", err)
|
||||
}
|
||||
args := flag.Args()
|
||||
|
||||
|
@ -81,49 +90,70 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Starting Statping v%v", VERSION))
|
||||
log.Info(fmt.Sprintf("Starting Statping v%v", VERSION))
|
||||
updateDisplay()
|
||||
|
||||
core.Configs, err = core.LoadConfigFile(utils.Directory)
|
||||
configs, err := core.LoadConfigFile(utils.Directory)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
core.SetupMode = true
|
||||
utils.Log(1, handlers.RunHTTPServer(ipAddress, port))
|
||||
os.Exit(1)
|
||||
writeAble, err := utils.DirWritable(utils.Directory)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if !writeAble {
|
||||
log.Fatalf("Statping does not have write permissions at: %v\nYou can change this directory by setting the STATPING_DIR environment variable to a dedicated path before starting.", utils.Directory)
|
||||
}
|
||||
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
mainProcess()
|
||||
core.CoreApp.Config = configs
|
||||
if err := mainProcess(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Close will gracefully stop the database connection, and log file
|
||||
func Close() {
|
||||
core.CloseDB()
|
||||
utils.CloseLogs()
|
||||
}
|
||||
|
||||
// sigterm will attempt to close the database connections gracefully
|
||||
func sigterm() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||
<-sigs
|
||||
Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// loadDotEnvs attempts to load database configs from a '.env' file in root directory
|
||||
func loadDotEnvs() error {
|
||||
err := godotenv.Load()
|
||||
err := godotenv.Load(envFile)
|
||||
if err == nil {
|
||||
utils.Log(1, "Environment file '.env' Loaded")
|
||||
UsingDotEnv = true
|
||||
log.Infoln("Environment file '.env' Loaded")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// mainProcess will initialize the Statping application and run the HTTP server
|
||||
func mainProcess() {
|
||||
func mainProcess() error {
|
||||
dir := utils.Directory
|
||||
var err error
|
||||
err = core.Configs.Connect(false, dir)
|
||||
err = core.CoreApp.Connect(false, dir)
|
||||
if err != nil {
|
||||
utils.Log(4, fmt.Sprintf("could not connect to database: %v", err))
|
||||
log.Errorln(fmt.Sprintf("could not connect to database: %v", err))
|
||||
return err
|
||||
}
|
||||
core.Configs.MigrateDatabase()
|
||||
core.CoreApp.MigrateDatabase()
|
||||
core.InitApp()
|
||||
if !core.SetupMode {
|
||||
plugin.LoadPlugins()
|
||||
fmt.Println(handlers.RunHTTPServer(ipAddress, port))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func ForEachPlugin() {
|
||||
if len(core.CoreApp.Plugins) > 0 {
|
||||
//for _, p := range core.Plugins {
|
||||
// p.OnShutdown()
|
||||
//}
|
||||
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
|
||||
// checkServices will start the checking go routine for each service
|
||||
func checkServices() {
|
||||
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
|
||||
log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
|
||||
for _, ser := range CoreApp.Services {
|
||||
//go obj.StartCheckins()
|
||||
go ser.CheckQueue(true)
|
||||
|
@ -40,6 +40,7 @@ func checkServices() {
|
|||
}
|
||||
|
||||
// Check will run checkHttp for HTTP services and checkTcp for TCP services
|
||||
// if record param is set to true, it will add a record into the database.
|
||||
func (s *Service) Check(record bool) {
|
||||
switch s.Type {
|
||||
case "http":
|
||||
|
@ -59,7 +60,7 @@ CheckLoop:
|
|||
for {
|
||||
select {
|
||||
case <-s.Running:
|
||||
utils.Log(1, fmt.Sprintf("Stopping service: %v", s.Name))
|
||||
log.Infoln(fmt.Sprintf("Stopping service: %v", s.Name))
|
||||
break CheckLoop
|
||||
case <-time.After(s.SleepDuration):
|
||||
s.Check(record)
|
||||
|
@ -210,9 +211,9 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
}
|
||||
|
||||
if s.Method == "POST" {
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, "application/json", headers, bytes.NewBuffer([]byte(s.PostData.String)), timeout)
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, "application/json", headers, bytes.NewBuffer([]byte(s.PostData.String)), timeout, s.VerifySSL.Bool)
|
||||
} else {
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, nil, headers, nil, timeout)
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, nil, headers, nil, timeout, s.VerifySSL.Bool)
|
||||
}
|
||||
if err != nil {
|
||||
if record {
|
||||
|
@ -228,7 +229,7 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
if s.Expected.String != "" {
|
||||
match, err := regexp.MatchString(s.Expected.String, string(content))
|
||||
if err != nil {
|
||||
utils.Log(2, fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String))
|
||||
log.Warnln(fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String))
|
||||
}
|
||||
if !match {
|
||||
if record {
|
||||
|
@ -243,7 +244,6 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
}
|
||||
return s
|
||||
}
|
||||
s.Online = true
|
||||
if record {
|
||||
recordSuccess(s)
|
||||
}
|
||||
|
@ -252,30 +252,35 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
|
||||
// recordSuccess will create a new 'hit' record in the database for a successful/online service
|
||||
func recordSuccess(s *Service) {
|
||||
s.Online = true
|
||||
s.LastOnline = utils.Timezoner(time.Now().UTC(), CoreApp.Timezone)
|
||||
hit := &types.Hit{
|
||||
Service: s.Id,
|
||||
Latency: s.Latency,
|
||||
PingTime: s.PingTime,
|
||||
CreatedAt: time.Now(),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
|
||||
s.CreateHit(hit)
|
||||
log.WithFields(utils.ToFields(hit, s.Select())).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
|
||||
notifier.OnSuccess(s.Service)
|
||||
s.Online = true
|
||||
s.SuccessNotified = true
|
||||
}
|
||||
|
||||
// recordFailure will create a new 'Failure' record in the database for a offline service
|
||||
func recordFailure(s *Service, issue string) {
|
||||
s.Online = false
|
||||
fail := &Failure{&types.Failure{
|
||||
fail := &types.Failure{
|
||||
Service: s.Id,
|
||||
Issue: issue,
|
||||
PingTime: s.PingTime,
|
||||
CreatedAt: time.Now(),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
ErrorCode: s.LastStatusCode,
|
||||
}}
|
||||
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
|
||||
}
|
||||
log.WithFields(utils.ToFields(fail, s.Select())).
|
||||
Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
|
||||
s.CreateFailure(fail)
|
||||
notifier.OnFailure(s.Service, fail.Failure)
|
||||
s.Online = false
|
||||
s.SuccessNotified = false
|
||||
s.UpdateNotify = CoreApp.UpdateNotify.Bool
|
||||
s.DownText = s.DowntimeText()
|
||||
notifier.OnFailure(s.Service, fail)
|
||||
}
|
||||
|
|
|
@ -47,14 +47,14 @@ CheckinLoop:
|
|||
for {
|
||||
select {
|
||||
case <-c.Running:
|
||||
utils.Log(1, fmt.Sprintf("Stopping checkin routine: %v", c.Name))
|
||||
log.Infoln(fmt.Sprintf("Stopping checkin routine: %v", c.Name))
|
||||
c.Failing = false
|
||||
break CheckinLoop
|
||||
case <-time.After(reCheck):
|
||||
utils.Log(1, fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
|
||||
log.Infoln(fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
|
||||
if c.Expected().Seconds() <= 0 {
|
||||
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt)
|
||||
utils.Log(3, issue)
|
||||
log.Errorln(issue)
|
||||
c.CreateFailure()
|
||||
}
|
||||
reCheck = c.Period()
|
||||
|
@ -143,7 +143,7 @@ func (c *Checkin) Grace() time.Duration {
|
|||
// Expected returns the duration of when the serviec should receive a Checkin
|
||||
func (c *Checkin) Expected() time.Duration {
|
||||
last := c.Last().CreatedAt
|
||||
now := time.Now()
|
||||
now := utils.Now()
|
||||
lastDir := now.Sub(last)
|
||||
sub := time.Duration(c.Period() - lastDir)
|
||||
return sub
|
||||
|
@ -213,7 +213,7 @@ func (c *Checkin) Create() (int64, error) {
|
|||
c.ApiKey = utils.RandomString(7)
|
||||
row := checkinDB().Create(&c)
|
||||
if row.Error != nil {
|
||||
utils.Log(2, row.Error)
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
service := SelectService(c.ServiceId)
|
||||
|
@ -227,7 +227,7 @@ func (c *Checkin) Create() (int64, error) {
|
|||
func (c *Checkin) Update() (int64, error) {
|
||||
row := checkinDB().Update(&c)
|
||||
if row.Error != nil {
|
||||
utils.Log(2, row.Error)
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
return c.Id, row.Error
|
||||
|
@ -236,11 +236,11 @@ func (c *Checkin) Update() (int64, error) {
|
|||
// Create will create a new successful checkinHit
|
||||
func (c *CheckinHit) Create() (int64, error) {
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = time.Now()
|
||||
c.CreatedAt = utils.Now()
|
||||
}
|
||||
row := checkinHitsDB().Create(&c)
|
||||
if row.Error != nil {
|
||||
utils.Log(2, row.Error)
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
return c.Id, row.Error
|
||||
|
@ -248,13 +248,13 @@ func (c *CheckinHit) Create() (int64, error) {
|
|||
|
||||
// Ago returns the duration of time between now and the last successful checkinHit
|
||||
func (c *CheckinHit) Ago() string {
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now(), c.CreatedAt)
|
||||
got, _ := timeago.TimeAgoWithTime(utils.Now(), c.CreatedAt)
|
||||
return got
|
||||
}
|
||||
|
||||
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
|
||||
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
|
||||
between := time.Now().Sub(time.Now()).Seconds()
|
||||
between := utils.Now().Sub(utils.Now()).Seconds()
|
||||
if between > float64(c.Interval) {
|
||||
fmt.Println("rechecking every 15 seconds!")
|
||||
time.Sleep(15 * time.Second)
|
||||
|
|
|
@ -31,12 +31,13 @@ type ErrorResponse struct {
|
|||
}
|
||||
|
||||
// LoadConfigFile will attempt to load the 'config.yml' file in a specific directory
|
||||
func LoadConfigFile(directory string) (*DbConfig, error) {
|
||||
var configs *DbConfig
|
||||
func LoadConfigFile(directory string) (*types.DbConfig, error) {
|
||||
var configs *types.DbConfig
|
||||
if os.Getenv("DB_CONN") != "" {
|
||||
utils.Log(1, "DB_CONN environment variable was found, waiting for database...")
|
||||
log.Warnln("DB_CONN environment variable was found, waiting for database...")
|
||||
return LoadUsingEnv()
|
||||
}
|
||||
log.Debugln("attempting to read config file at: " + directory + "/config.yml")
|
||||
file, err := ioutil.ReadFile(directory + "/config.yml")
|
||||
if err != nil {
|
||||
return nil, errors.New("config.yml file not found at " + directory + "/config.yml - starting in setup mode")
|
||||
|
@ -45,12 +46,13 @@ func LoadConfigFile(directory string) (*DbConfig, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Configs = configs
|
||||
return Configs, err
|
||||
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + directory + "/config.yml")
|
||||
CoreApp.Config = configs
|
||||
return configs, err
|
||||
}
|
||||
|
||||
// LoadUsingEnv will attempt to load database configs based on environment variables. If DB_CONN is set if will force this function.
|
||||
func LoadUsingEnv() (*DbConfig, error) {
|
||||
func LoadUsingEnv() (*types.DbConfig, error) {
|
||||
Configs, err := EnvToConfig()
|
||||
if err != nil {
|
||||
return Configs, err
|
||||
|
@ -61,23 +63,22 @@ func LoadUsingEnv() (*DbConfig, error) {
|
|||
} else {
|
||||
CoreApp.Domain = os.Getenv("DOMAIN")
|
||||
}
|
||||
CoreApp.DbConnection = Configs.DbConn
|
||||
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
|
||||
|
||||
err = Configs.Connect(true, utils.Directory)
|
||||
err = CoreApp.Connect(true, utils.Directory)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
Configs.Save()
|
||||
CoreApp.SaveConfig(Configs)
|
||||
exists := DbSession.HasTable("core")
|
||||
if !exists {
|
||||
utils.Log(1, fmt.Sprintf("Core database does not exist, creating now!"))
|
||||
Configs.DropDatabase()
|
||||
Configs.CreateDatabase()
|
||||
CoreApp, err = Configs.InsertCore()
|
||||
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
|
||||
CoreApp.DropDatabase()
|
||||
CoreApp.CreateDatabase()
|
||||
CoreApp, err = CoreApp.InsertCore(Configs)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
username := os.Getenv("ADMIN_USER")
|
||||
|
@ -103,8 +104,8 @@ func LoadUsingEnv() (*DbConfig, error) {
|
|||
return Configs, nil
|
||||
}
|
||||
|
||||
// DefaultPort accepts a database type and returns its default port
|
||||
func DefaultPort(db string) int64 {
|
||||
// defaultPort accepts a database type and returns its default port
|
||||
func defaultPort(db string) int64 {
|
||||
switch db {
|
||||
case "mysql":
|
||||
return 3306
|
||||
|
@ -118,29 +119,28 @@ func DefaultPort(db string) int64 {
|
|||
}
|
||||
|
||||
// EnvToConfig converts environment variables to a DbConfig variable
|
||||
func EnvToConfig() (*DbConfig, error) {
|
||||
Configs = new(DbConfig)
|
||||
func EnvToConfig() (*types.DbConfig, error) {
|
||||
var err error
|
||||
if os.Getenv("DB_CONN") == "" {
|
||||
return Configs, errors.New("Missing DB_CONN environment variable")
|
||||
return nil, errors.New("Missing DB_CONN environment variable")
|
||||
}
|
||||
if os.Getenv("DB_CONN") != "sqlite" {
|
||||
if os.Getenv("DB_HOST") == "" {
|
||||
return Configs, errors.New("Missing DB_HOST environment variable")
|
||||
return nil, errors.New("Missing DB_HOST environment variable")
|
||||
}
|
||||
if os.Getenv("DB_USER") == "" {
|
||||
return Configs, errors.New("Missing DB_USER environment variable")
|
||||
return nil, errors.New("Missing DB_USER environment variable")
|
||||
}
|
||||
if os.Getenv("DB_PASS") == "" {
|
||||
return Configs, errors.New("Missing DB_PASS environment variable")
|
||||
return nil, errors.New("Missing DB_PASS environment variable")
|
||||
}
|
||||
if os.Getenv("DB_DATABASE") == "" {
|
||||
return Configs, errors.New("Missing DB_DATABASE environment variable")
|
||||
return nil, errors.New("Missing DB_DATABASE environment variable")
|
||||
}
|
||||
}
|
||||
port := utils.ToInt(os.Getenv("DB_PORT"))
|
||||
if port == 0 {
|
||||
port = DefaultPort(os.Getenv("DB_PORT"))
|
||||
port = defaultPort(os.Getenv("DB_PORT"))
|
||||
}
|
||||
name := os.Getenv("NAME")
|
||||
if name == "" {
|
||||
|
@ -161,7 +161,7 @@ func EnvToConfig() (*DbConfig, error) {
|
|||
adminPass = "admin"
|
||||
}
|
||||
|
||||
Configs = &DbConfig{
|
||||
configs := &types.DbConfig{
|
||||
DbConn: os.Getenv("DB_CONN"),
|
||||
DbHost: os.Getenv("DB_HOST"),
|
||||
DbUser: os.Getenv("DB_USER"),
|
||||
|
@ -176,16 +176,20 @@ func EnvToConfig() (*DbConfig, error) {
|
|||
Password: adminPass,
|
||||
Error: nil,
|
||||
Location: utils.Directory,
|
||||
SqlFile: os.Getenv("SQL_FILE"),
|
||||
}
|
||||
return Configs, err
|
||||
CoreApp.Config = configs
|
||||
return configs, err
|
||||
}
|
||||
|
||||
// SampleData runs all the sample data for a new Statping installation
|
||||
func SampleData() error {
|
||||
if err := InsertSampleData(); err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
if err := InsertSampleHits(); err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -193,9 +197,10 @@ func SampleData() error {
|
|||
|
||||
// DeleteConfig will delete the 'config.yml' file
|
||||
func DeleteConfig() error {
|
||||
err := os.Remove(utils.Directory + "/config.yml")
|
||||
log.Debugln("deleting config yaml file", utils.Directory+"/config.yml")
|
||||
err := utils.DeleteFile(utils.Directory + "/config.yml")
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
42
core/core.go
42
core/core.go
|
@ -18,7 +18,9 @@ package core
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/integrations"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/notifiers"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
|
@ -35,10 +37,10 @@ type Core struct {
|
|||
}
|
||||
|
||||
var (
|
||||
Configs *DbConfig // Configs holds all of the config.yml and database info
|
||||
CoreApp *Core // CoreApp is a global variable that contains many elements
|
||||
SetupMode bool // SetupMode will be true if Statping does not have a database connection
|
||||
VERSION string // VERSION is set on build automatically by setting a -ldflag
|
||||
CoreApp *Core // CoreApp is a global variable that contains many elements
|
||||
SetupMode bool // SetupMode will be true if Statping does not have a database connection
|
||||
VERSION string // VERSION is set on build automatically by setting a -ldflag
|
||||
log = utils.Log.WithField("type", "core")
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -47,9 +49,10 @@ func init() {
|
|||
|
||||
// NewCore return a new *core.Core struct
|
||||
func NewCore() *Core {
|
||||
CoreApp = new(Core)
|
||||
CoreApp.Core = new(types.Core)
|
||||
CoreApp.Started = time.Now()
|
||||
CoreApp = &Core{&types.Core{
|
||||
Started: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
return CoreApp
|
||||
}
|
||||
|
||||
|
@ -64,14 +67,17 @@ func InitApp() {
|
|||
InsertNotifierDB()
|
||||
CoreApp.SelectAllServices(true)
|
||||
checkServices()
|
||||
CoreApp.Notifications = notifier.Load()
|
||||
AttachNotifiers()
|
||||
CoreApp.Notifications = notifier.AllCommunications
|
||||
CoreApp.Integrations = integrations.Integrations
|
||||
go DatabaseMaintence()
|
||||
SetupMode = false
|
||||
}
|
||||
|
||||
// InsertNotifierDB inject the Statping database instance to the Notifier package
|
||||
func InsertNotifierDB() error {
|
||||
if DbSession == nil {
|
||||
err := Configs.Connect(false, utils.Directory)
|
||||
err := CoreApp.Connect(false, utils.Directory)
|
||||
if err != nil {
|
||||
return errors.New("database connection has not been created")
|
||||
}
|
||||
|
@ -144,17 +150,18 @@ func (c Core) AllOnline() bool {
|
|||
// SelectCore will return the CoreApp global variable and the settings/configs for Statping
|
||||
func SelectCore() (*Core, error) {
|
||||
if DbSession == nil {
|
||||
log.Traceln("database has not been initiated yet.")
|
||||
return nil, errors.New("database has not been initiated yet.")
|
||||
}
|
||||
exists := DbSession.HasTable("core")
|
||||
if !exists {
|
||||
log.Errorf("core database has not been setup yet, does not have the 'core' table")
|
||||
return nil, errors.New("core database has not been setup yet.")
|
||||
}
|
||||
db := coreDB().First(&CoreApp)
|
||||
if db.Error != nil {
|
||||
return nil, db.Error
|
||||
}
|
||||
CoreApp.DbConnection = Configs.DbConn
|
||||
CoreApp.Version = VERSION
|
||||
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
|
||||
return CoreApp, db.Error
|
||||
|
@ -177,6 +184,21 @@ func GetLocalIP() string {
|
|||
return "http://localhost"
|
||||
}
|
||||
|
||||
// AttachNotifiers will attach all the notifier's into the system
|
||||
func AttachNotifiers() error {
|
||||
return notifier.AddNotifiers(
|
||||
notifiers.Command,
|
||||
notifiers.Discorder,
|
||||
notifiers.Emailer,
|
||||
notifiers.LineNotify,
|
||||
notifiers.Mobile,
|
||||
notifiers.Slacker,
|
||||
notifiers.Telegram,
|
||||
notifiers.Twilio,
|
||||
notifiers.Webhook,
|
||||
)
|
||||
}
|
||||
|
||||
// ServiceOrder will reorder the services based on 'order_id' (Order)
|
||||
type ServiceOrder []types.ServiceInterface
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
@ -37,31 +38,27 @@ func init() {
|
|||
}
|
||||
|
||||
func TestNewCore(t *testing.T) {
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
utils.DeleteFile(dir + "/config.yml")
|
||||
utils.DeleteFile(dir + "/statup.db")
|
||||
CoreApp = NewCore()
|
||||
assert.NotNil(t, CoreApp)
|
||||
CoreApp.Name = "Tester"
|
||||
err := TmpRecords("core.db")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, CoreApp)
|
||||
}
|
||||
|
||||
func TestDbConfig_Save(t *testing.T) {
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
var err error
|
||||
Configs = &DbConfig{
|
||||
DbConn: "sqlite",
|
||||
Project: "Tester",
|
||||
Location: dir,
|
||||
}
|
||||
Configs, err = Configs.Save()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "sqlite", Configs.DbConn)
|
||||
assert.NotEmpty(t, Configs.ApiKey)
|
||||
assert.NotEmpty(t, Configs.ApiSecret)
|
||||
t.SkipNow()
|
||||
//if skipNewDb {
|
||||
// t.SkipNow()
|
||||
//}
|
||||
//var err error
|
||||
//Configs = &DbConfig{
|
||||
// DbConn: "sqlite",
|
||||
// Project: "Tester",
|
||||
// Location: dir,
|
||||
//}
|
||||
//Configs, err = Configs.Save()
|
||||
//assert.Nil(t, err)
|
||||
//assert.Equal(t, "sqlite", Configs.DbConn)
|
||||
//assert.NotEmpty(t, Configs.ApiKey)
|
||||
//assert.NotEmpty(t, Configs.ApiSecret)
|
||||
}
|
||||
|
||||
func TestLoadDbConfig(t *testing.T) {
|
||||
|
@ -71,43 +68,44 @@ func TestLoadDbConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDbConnection(t *testing.T) {
|
||||
err := Configs.Connect(false, dir)
|
||||
err := CoreApp.Connect(false, dir)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestDropDatabase(t *testing.T) {
|
||||
t.SkipNow()
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
err := Configs.DropDatabase()
|
||||
err := CoreApp.DropDatabase()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSeedSchemaDatabase(t *testing.T) {
|
||||
t.SkipNow()
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
err := Configs.CreateDatabase()
|
||||
err := CoreApp.CreateDatabase()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestMigrateDatabase(t *testing.T) {
|
||||
err := Configs.MigrateDatabase()
|
||||
t.SkipNow()
|
||||
err := CoreApp.MigrateDatabase()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSeedDatabase(t *testing.T) {
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.SkipNow()
|
||||
err := InsertLargeSampleData()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestReLoadDbConfig(t *testing.T) {
|
||||
err := Configs.Connect(false, dir)
|
||||
err := CoreApp.Connect(false, dir)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "sqlite", Configs.DbConn)
|
||||
assert.Equal(t, "sqlite", CoreApp.Config.DbConn)
|
||||
}
|
||||
|
||||
func TestSelectCore(t *testing.T) {
|
||||
|
@ -117,6 +115,7 @@ func TestSelectCore(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInsertNotifierDB(t *testing.T) {
|
||||
t.SkipNow()
|
||||
if skipNewDb {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
@ -134,6 +133,7 @@ func TestEnvToConfig(t *testing.T) {
|
|||
os.Setenv("DESCRIPTION", "Testing Statping")
|
||||
os.Setenv("ADMIN_USER", "admin")
|
||||
os.Setenv("ADMIN_PASS", "admin123")
|
||||
os.Setenv("VERBOSE", "1")
|
||||
config, err := EnvToConfig()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, config.DbConn, "sqlite")
|
||||
|
|
249
core/database.go
249
core/database.go
|
@ -26,6 +26,7 @@ import (
|
|||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -37,6 +38,10 @@ var (
|
|||
|
||||
func init() {
|
||||
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}}
|
||||
|
||||
gorm.NowFunc = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
}
|
||||
|
||||
// DbConfig stores the config.yml file for the statup configuration
|
||||
|
@ -100,7 +105,7 @@ func incidentsUpdatesDB() *gorm.DB {
|
|||
// HitsBetween returns the gorm database query for a collection of service hits between a time range
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) *gorm.DB {
|
||||
selector := Dbtimestamp(group, column)
|
||||
if CoreApp.DbConnection == "postgres" {
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||
} else {
|
||||
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
|
||||
|
@ -114,137 +119,166 @@ func CloseDB() {
|
|||
}
|
||||
}
|
||||
|
||||
// AfterFind for Core will set the timezone
|
||||
func (c *Core) AfterFind() (err error) {
|
||||
c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for Service will set the timezone
|
||||
func (s *Service) AfterFind() (err error) {
|
||||
s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone)
|
||||
s.UpdatedAt = utils.Timezoner(s.UpdatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for Hit will set the timezone
|
||||
func (h *Hit) AfterFind() (err error) {
|
||||
h.CreatedAt = utils.Timezoner(h.CreatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for Failure will set the timezone
|
||||
func (f *Failure) AfterFind() (err error) {
|
||||
f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for USer will set the timezone
|
||||
func (u *User) AfterFind() (err error) {
|
||||
u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
|
||||
u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for Checkin will set the timezone
|
||||
func (c *Checkin) AfterFind() (err error) {
|
||||
c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for checkinHit will set the timezone
|
||||
func (c *CheckinHit) AfterFind() (err error) {
|
||||
c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
|
||||
// AfterFind for Message will set the timezone
|
||||
func (u *Message) AfterFind() (err error) {
|
||||
u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
|
||||
u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
|
||||
u.StartOn = utils.Timezoner(u.StartOn.UTC(), CoreApp.Timezone)
|
||||
u.EndOn = utils.Timezoner(u.EndOn.UTC(), CoreApp.Timezone)
|
||||
return
|
||||
}
|
||||
//// AfterFind for Core will set the timezone
|
||||
//func (c *Core) AfterFind() (err error) {
|
||||
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
// c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Service will set the timezone
|
||||
//func (s *Service) AfterFind() (err error) {
|
||||
// s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone)
|
||||
// s.UpdatedAt = utils.Timezoner(s.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Hit will set the timezone
|
||||
//func (h *Hit) AfterFind() (err error) {
|
||||
// h.CreatedAt = utils.Timezoner(h.CreatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Failure will set the timezone
|
||||
//func (f *Failure) AfterFind() (err error) {
|
||||
// f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for USer will set the timezone
|
||||
//func (u *User) AfterFind() (err error) {
|
||||
// u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
|
||||
// u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Checkin will set the timezone
|
||||
//func (c *Checkin) AfterFind() (err error) {
|
||||
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
// c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for checkinHit will set the timezone
|
||||
//func (c *CheckinHit) AfterFind() (err error) {
|
||||
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// AfterFind for Message will set the timezone
|
||||
//func (u *Message) AfterFind() (err error) {
|
||||
// u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
|
||||
// u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
|
||||
// u.StartOn = utils.Timezoner(u.StartOn.UTC(), CoreApp.Timezone)
|
||||
// u.EndOn = utils.Timezoner(u.EndOn.UTC(), CoreApp.Timezone)
|
||||
// return
|
||||
//}
|
||||
|
||||
// InsertCore create the single row for the Core settings in Statping
|
||||
func (db *DbConfig) InsertCore() (*Core, error) {
|
||||
func (c *Core) InsertCore(db *types.DbConfig) (*Core, error) {
|
||||
CoreApp = &Core{Core: &types.Core{
|
||||
Name: db.Project,
|
||||
Description: db.Description,
|
||||
Config: "config.yml",
|
||||
ConfigFile: "config.yml",
|
||||
ApiKey: utils.NewSHA1Hash(9),
|
||||
ApiSecret: utils.NewSHA1Hash(16),
|
||||
Domain: db.Domain,
|
||||
MigrationId: time.Now().Unix(),
|
||||
Config: db,
|
||||
}}
|
||||
CoreApp.DbConnection = db.DbConn
|
||||
query := coreDB().Create(&CoreApp)
|
||||
return CoreApp, query.Error
|
||||
}
|
||||
|
||||
func findDbFile() string {
|
||||
if CoreApp.Config.SqlFile != "" {
|
||||
return CoreApp.Config.SqlFile
|
||||
}
|
||||
filename := types.SqliteFilename
|
||||
err := filepath.Walk(utils.Directory, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if filepath.Ext(path) == ".db" {
|
||||
filename = info.Name()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
// Connect will attempt to connect to the sqlite, postgres, or mysql database
|
||||
func (db *DbConfig) Connect(retry bool, location string) error {
|
||||
func (c *Core) Connect(retry bool, location string) error {
|
||||
postgresSSL := os.Getenv("POSTGRES_SSLMODE")
|
||||
if DbSession != nil {
|
||||
return nil
|
||||
}
|
||||
var conn, dbType string
|
||||
var err error
|
||||
dbType = Configs.DbConn
|
||||
if Configs.DbPort == 0 {
|
||||
Configs.DbPort = DefaultPort(dbType)
|
||||
dbType = CoreApp.Config.DbConn
|
||||
if CoreApp.Config.DbPort == 0 {
|
||||
CoreApp.Config.DbPort = defaultPort(dbType)
|
||||
}
|
||||
switch dbType {
|
||||
case "sqlite":
|
||||
conn = location + "/statup.db"
|
||||
sqlFilename := findDbFile()
|
||||
conn = sqlFilename
|
||||
log.Infof("SQL database file at: %v/%v", utils.Directory, conn)
|
||||
dbType = "sqlite3"
|
||||
case "mysql":
|
||||
host := fmt.Sprintf("%v:%v", Configs.DbHost, Configs.DbPort)
|
||||
conn = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8&parseTime=True&loc=UTC&time_zone=%%27UTC%%27", Configs.DbUser, Configs.DbPass, host, Configs.DbData)
|
||||
host := fmt.Sprintf("%v:%v", CoreApp.Config.DbHost, CoreApp.Config.DbPort)
|
||||
conn = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8&parseTime=True&loc=UTC&time_zone=%%27UTC%%27", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
||||
case "postgres":
|
||||
sslMode := "disable"
|
||||
if postgresSSL != "" {
|
||||
sslMode = postgresSSL
|
||||
}
|
||||
conn = fmt.Sprintf("host=%v port=%v user=%v dbname=%v password=%v timezone=UTC sslmode=%v", Configs.DbHost, Configs.DbPort, Configs.DbUser, Configs.DbData, Configs.DbPass, sslMode)
|
||||
conn = fmt.Sprintf("host=%v port=%v user=%v dbname=%v password=%v timezone=UTC sslmode=%v", CoreApp.Config.DbHost, CoreApp.Config.DbPort, CoreApp.Config.DbUser, CoreApp.Config.DbData, CoreApp.Config.DbPass, sslMode)
|
||||
case "mssql":
|
||||
host := fmt.Sprintf("%v:%v", Configs.DbHost, Configs.DbPort)
|
||||
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", Configs.DbUser, Configs.DbPass, host, Configs.DbData)
|
||||
host := fmt.Sprintf("%v:%v", CoreApp.Config.DbHost, CoreApp.Config.DbPort)
|
||||
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
||||
}
|
||||
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
|
||||
dbSession, err := gorm.Open(dbType, conn)
|
||||
if err != nil {
|
||||
log.Debugln(fmt.Sprintf("Database connection error %v", err))
|
||||
if retry {
|
||||
utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", conn))
|
||||
return db.waitForDb()
|
||||
log.Errorln(fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", CoreApp.Config.DbHost))
|
||||
return c.waitForDb()
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if dbType == "sqlite3" {
|
||||
dbSession.DB().SetMaxOpenConns(1)
|
||||
}
|
||||
err = dbSession.DB().Ping()
|
||||
if err == nil {
|
||||
log.WithFields(utils.ToFields(dbSession)).Debugln("connected to database")
|
||||
|
||||
dbSession.DB().SetMaxOpenConns(5)
|
||||
dbSession.DB().SetMaxIdleConns(5)
|
||||
dbSession.DB().SetConnMaxLifetime(1 * time.Minute)
|
||||
|
||||
if dbSession.DB().Ping() == nil {
|
||||
DbSession = dbSession
|
||||
utils.Log(1, fmt.Sprintf("Database %v connection '%v@%v:%v' at %v was successful.", dbType, Configs.DbUser, Configs.DbHost, Configs.DbPort, Configs.DbData))
|
||||
if utils.VerboseMode >= 4 {
|
||||
DbSession.LogMode(true).Debug().SetLogger(log)
|
||||
}
|
||||
log.Infoln(fmt.Sprintf("Database %v connection was successful.", dbType))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// waitForDb will sleep for 5 seconds and try to connect to the database again
|
||||
func (db *DbConfig) waitForDb() error {
|
||||
func (c *Core) waitForDb() error {
|
||||
time.Sleep(5 * time.Second)
|
||||
return db.Connect(true, utils.Directory)
|
||||
return c.Connect(true, utils.Directory)
|
||||
}
|
||||
|
||||
// DatabaseMaintence will automatically delete old records from 'failures' and 'hits'
|
||||
// this function is currently set to delete records 7+ days old every 60 minutes
|
||||
func DatabaseMaintence() {
|
||||
for range time.Tick(60 * time.Minute) {
|
||||
utils.Log(1, "Checking for database records older than 3 months...")
|
||||
log.Infoln("Checking for database records older than 3 months...")
|
||||
since := time.Now().AddDate(0, -3, 0).UTC()
|
||||
DeleteAllSince("failures", since)
|
||||
DeleteAllSince("hits", since)
|
||||
|
@ -256,21 +290,21 @@ func DeleteAllSince(table string, date time.Time) {
|
|||
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
|
||||
db := DbSession.Exec(sql)
|
||||
if db.Error != nil {
|
||||
utils.Log(2, db.Error)
|
||||
log.Warnln(db.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// Update will save the config.yml file
|
||||
func (db *DbConfig) Update() error {
|
||||
func (c *Core) UpdateConfig() error {
|
||||
var err error
|
||||
config, err := os.Create(utils.Directory + "/config.yml")
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
data, err := yaml.Marshal(db)
|
||||
data, err := yaml.Marshal(c.Config)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
config.WriteString(string(data))
|
||||
|
@ -279,31 +313,33 @@ func (db *DbConfig) Update() error {
|
|||
}
|
||||
|
||||
// Save will initially create the config.yml file
|
||||
func (db *DbConfig) Save() (*DbConfig, error) {
|
||||
var err error
|
||||
func (c *Core) SaveConfig(configs *types.DbConfig) (*types.DbConfig, error) {
|
||||
config, err := os.Create(utils.Directory + "/config.yml")
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
db.ApiKey = utils.NewSHA1Hash(16)
|
||||
db.ApiSecret = utils.NewSHA1Hash(16)
|
||||
data, err := yaml.Marshal(db)
|
||||
defer config.Close()
|
||||
log.WithFields(utils.ToFields(configs)).Debugln("saving config file at: " + utils.Directory + "/config.yml")
|
||||
c.Config = configs
|
||||
c.Config.ApiKey = utils.NewSHA1Hash(16)
|
||||
c.Config.ApiSecret = utils.NewSHA1Hash(16)
|
||||
data, err := yaml.Marshal(configs)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
config.WriteString(string(data))
|
||||
defer config.Close()
|
||||
return db, err
|
||||
log.WithFields(utils.ToFields(configs)).Infoln("saved config file at: " + utils.Directory + "/config.yml")
|
||||
return c.Config, err
|
||||
}
|
||||
|
||||
// CreateCore will initialize the global variable 'CoreApp". This global variable contains most of Statping app.
|
||||
func (c *DbConfig) CreateCore() *Core {
|
||||
func (c *Core) CreateCore() *Core {
|
||||
newCore := &types.Core{
|
||||
Name: c.Project,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Config: "config.yml",
|
||||
ConfigFile: utils.Directory + "/config.yml",
|
||||
ApiKey: c.ApiKey,
|
||||
ApiSecret: c.ApiSecret,
|
||||
Domain: c.Domain,
|
||||
|
@ -315,14 +351,14 @@ func (c *DbConfig) CreateCore() *Core {
|
|||
}
|
||||
CoreApp, err := SelectCore()
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
return CoreApp
|
||||
}
|
||||
|
||||
// DropDatabase will DROP each table Statping created
|
||||
func (db *DbConfig) DropDatabase() error {
|
||||
utils.Log(1, "Dropping Database Tables...")
|
||||
func (c *Core) DropDatabase() error {
|
||||
log.Infoln("Dropping Database Tables...")
|
||||
err := DbSession.DropTableIfExists("checkins")
|
||||
err = DbSession.DropTableIfExists("checkin_hits")
|
||||
err = DbSession.DropTableIfExists("notifications")
|
||||
|
@ -338,9 +374,9 @@ func (db *DbConfig) DropDatabase() error {
|
|||
}
|
||||
|
||||
// CreateDatabase will CREATE TABLES for each of the Statping elements
|
||||
func (db *DbConfig) CreateDatabase() error {
|
||||
func (c *Core) CreateDatabase() error {
|
||||
var err error
|
||||
utils.Log(1, "Creating Database Tables...")
|
||||
log.Infoln("Creating Database Tables...")
|
||||
for _, table := range DbModels {
|
||||
if err := DbSession.CreateTable(table); err.Error != nil {
|
||||
return err.Error
|
||||
|
@ -349,15 +385,15 @@ func (db *DbConfig) CreateDatabase() error {
|
|||
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
utils.Log(1, "Statping Database Created")
|
||||
log.Infoln("Statping Database Created")
|
||||
return err
|
||||
}
|
||||
|
||||
// MigrateDatabase will migrate the database structure to current version.
|
||||
// This function will NOT remove previous records, tables or columns from the database.
|
||||
// If this function has an issue, it will ROLLBACK to the previous state.
|
||||
func (db *DbConfig) MigrateDatabase() error {
|
||||
utils.Log(1, "Migrating Database Tables...")
|
||||
func (c *Core) MigrateDatabase() error {
|
||||
log.Infoln("Migrating Database Tables...")
|
||||
tx := DbSession.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -365,6 +401,7 @@ func (db *DbConfig) MigrateDatabase() error {
|
|||
}
|
||||
}()
|
||||
if tx.Error != nil {
|
||||
log.Errorln(tx.Error)
|
||||
return tx.Error
|
||||
}
|
||||
for _, table := range DbModels {
|
||||
|
@ -372,9 +409,9 @@ func (db *DbConfig) MigrateDatabase() error {
|
|||
}
|
||||
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error != nil {
|
||||
tx.Rollback()
|
||||
utils.Log(3, fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
|
||||
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
|
||||
return tx.Error
|
||||
}
|
||||
utils.Log(1, "Statping Database Migrated")
|
||||
log.Infoln("Statping Database Migrated")
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"encoding/json"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
|
@ -28,7 +27,7 @@ import (
|
|||
func ExportChartsJs() string {
|
||||
render, err := source.JsBox.String("charts.js")
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
t := template.New("charts")
|
||||
t.Funcs(template.FuncMap{
|
||||
|
@ -39,7 +38,7 @@ func ExportChartsJs() string {
|
|||
t.Parse(render)
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, CoreApp.Services); err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
result := tpl.String()
|
||||
return result
|
||||
|
|
|
@ -19,7 +19,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -35,16 +34,15 @@ const (
|
|||
)
|
||||
|
||||
// CreateFailure will create a new Failure record for a service
|
||||
func (s *Service) CreateFailure(fail types.FailureInterface) (int64, error) {
|
||||
f := fail.(*Failure)
|
||||
func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
|
||||
f.Service = s.Id
|
||||
row := failuresDB().Create(f)
|
||||
if row.Error != nil {
|
||||
utils.Log(3, row.Error)
|
||||
log.Errorln(row.Error)
|
||||
return 0, row.Error
|
||||
}
|
||||
sort.Sort(types.FailSort(s.Failures))
|
||||
s.Failures = append(s.Failures, f)
|
||||
//s.Failures = append(s.Failures, f)
|
||||
if len(s.Failures) > limitedFailures {
|
||||
s.Failures = s.Failures[1:]
|
||||
}
|
||||
|
@ -57,7 +55,7 @@ func (s *Service) AllFailures() []*Failure {
|
|||
col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc")
|
||||
err := col.Find(&fails)
|
||||
if err.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||
return nil
|
||||
}
|
||||
return fails
|
||||
|
@ -67,7 +65,7 @@ func (s *Service) AllFailures() []*Failure {
|
|||
func (s *Service) DeleteFailures() {
|
||||
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
|
||||
if err.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err))
|
||||
log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err))
|
||||
}
|
||||
s.Failures = nil
|
||||
}
|
||||
|
@ -88,7 +86,7 @@ func (s *Service) LimitedCheckinFailures(amount int64) []*Failure {
|
|||
|
||||
// Ago returns a human readable timestamp for a Failure
|
||||
func (f *Failure) Ago() string {
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().UTC(), f.CreatedAt)
|
||||
return got
|
||||
}
|
||||
|
||||
|
@ -119,7 +117,7 @@ func CountFailures() uint64 {
|
|||
var count uint64
|
||||
err := failuresDB().Count(&count)
|
||||
if err.Error != nil {
|
||||
utils.Log(2, err.Error)
|
||||
log.Warnln(err.Error)
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
|
@ -137,7 +135,7 @@ func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
|
|||
|
||||
// TotalFailures24 returns the amount of failures for a service within the last 24 hours
|
||||
func (s *Service) TotalFailures24() (uint64, error) {
|
||||
ago := time.Now().Add(-24 * time.Hour)
|
||||
ago := time.Now().UTC().Add(-24 * time.Hour)
|
||||
return s.TotalFailuresSince(ago)
|
||||
}
|
||||
|
||||
|
@ -151,7 +149,7 @@ func (s *Service) TotalFailures() (uint64, error) {
|
|||
|
||||
// FailuresDaysAgo returns the amount of failures since days ago
|
||||
func (s *Service) FailuresDaysAgo(days int) uint64 {
|
||||
ago := time.Now().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
count, _ := s.TotalFailuresSince(ago)
|
||||
return count
|
||||
}
|
||||
|
|
|
@ -22,14 +22,14 @@ func (g *Group) Delete() error {
|
|||
|
||||
// Create will create a group and insert it into the database
|
||||
func (g *Group) Create() (int64, error) {
|
||||
g.CreatedAt = time.Now()
|
||||
g.CreatedAt = time.Now().UTC()
|
||||
db := groupsDb().Create(g)
|
||||
return g.Id, db.Error
|
||||
}
|
||||
|
||||
// Update will update a group
|
||||
func (g *Group) Update() (int64, error) {
|
||||
g.UpdatedAt = time.Now()
|
||||
g.UpdatedAt = time.Now().UTC()
|
||||
db := groupsDb().Update(g)
|
||||
return g.Id, db.Error
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ package core
|
|||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -29,7 +28,7 @@ type Hit struct {
|
|||
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
|
||||
db := hitsDB().Create(&h)
|
||||
if db.Error != nil {
|
||||
utils.Log(2, db.Error)
|
||||
log.Errorln(db.Error)
|
||||
return 0, db.Error
|
||||
}
|
||||
return h.Id, db.Error
|
||||
|
|
|
@ -47,14 +47,14 @@ func (i *Incident) Delete() error {
|
|||
|
||||
// Create will create a incident and insert it into the database
|
||||
func (i *Incident) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now()
|
||||
i.CreatedAt = time.Now().UTC()
|
||||
db := incidentsDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
||||
// Update will update a incident
|
||||
func (i *Incident) Update() (int64, error) {
|
||||
i.UpdatedAt = time.Now()
|
||||
i.UpdatedAt = time.Now().UTC()
|
||||
db := incidentsDB().Update(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func (i *IncidentUpdate) Delete() error {
|
|||
|
||||
// Create will create a incident update and insert it into the database
|
||||
func (i *IncidentUpdate) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now()
|
||||
i.CreatedAt = time.Now().UTC()
|
||||
db := incidentsUpdatesDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const requiredSize = 17
|
||||
|
||||
type csvIntegration struct {
|
||||
*types.Integration
|
||||
}
|
||||
|
||||
var csvIntegrator = &csvIntegration{&types.Integration{
|
||||
ShortName: "csv",
|
||||
Name: "CSV File",
|
||||
Icon: "<i class=\"fas fa-file-csv\"></i>",
|
||||
Description: "Import multiple services from a CSV file. Please have your CSV file formatted with the correct amount of columns based on the <a href=\"https://raw.githubusercontent.com/hunterlong/statping/master/source/tmpl/bulk_import.csv\">example file on Github</a>.",
|
||||
Fields: []*types.IntegrationField{
|
||||
{
|
||||
Name: "input",
|
||||
Type: "textarea",
|
||||
Description: "",
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
var csvData [][]string
|
||||
|
||||
func (t *csvIntegration) Get() *types.Integration {
|
||||
return t.Integration
|
||||
}
|
||||
|
||||
func (t *csvIntegration) List() ([]*types.Service, error) {
|
||||
data := Value(t, "input").(string)
|
||||
buf := bytes.NewReader([]byte(data))
|
||||
r := csv.NewReader(buf)
|
||||
records, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var services []*types.Service
|
||||
for k, v := range records[1:] {
|
||||
s, err := commaToService(v)
|
||||
if err != nil {
|
||||
log.Errorf("error on line %v: %v", k, err)
|
||||
continue
|
||||
}
|
||||
services = append(services, s)
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// commaToService will convert a CSV comma delimited string slice to a Service type
|
||||
// this function is used for the bulk import services feature
|
||||
func commaToService(s []string) (*types.Service, error) {
|
||||
if len(s) != requiredSize {
|
||||
err := fmt.Errorf("file has %v columns of data, not the expected amount of %v columns for a service", len(s), requiredSize)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval, err := time.ParseDuration(s[4])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse internal duration: " + s[4])
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(s[9])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse timeout duration: " + s[9])
|
||||
}
|
||||
|
||||
allowNotifications, err := strconv.ParseBool(s[11])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse allow notifications boolean: " + s[11])
|
||||
}
|
||||
|
||||
public, err := strconv.ParseBool(s[12])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse public boolean: " + s[12])
|
||||
}
|
||||
|
||||
verifySsl, err := strconv.ParseBool(s[16])
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse verifiy SSL boolean: " + s[16])
|
||||
}
|
||||
|
||||
newService := &types.Service{
|
||||
Name: s[0],
|
||||
Domain: s[1],
|
||||
Expected: types.NewNullString(s[2]),
|
||||
ExpectedStatus: int(utils.ToInt(s[3])),
|
||||
Interval: int(utils.ToInt(interval.Seconds())),
|
||||
Type: s[5],
|
||||
Method: s[6],
|
||||
PostData: types.NewNullString(s[7]),
|
||||
Port: int(utils.ToInt(s[8])),
|
||||
Timeout: int(utils.ToInt(timeout.Seconds())),
|
||||
AllowNotifications: types.NewNullBool(allowNotifications),
|
||||
Public: types.NewNullBool(public),
|
||||
GroupId: int(utils.ToInt(s[13])),
|
||||
Headers: types.NewNullString(s[14]),
|
||||
Permalink: types.NewNullString(s[15]),
|
||||
VerifySSL: types.NewNullBool(verifySsl),
|
||||
}
|
||||
|
||||
return newService, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCsvFileIntegration(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("../../source/tmpl/bulk_import.csv")
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("Set Field Value", func(t *testing.T) {
|
||||
formPost := map[string][]string{}
|
||||
formPost["input"] = []string{string(data)}
|
||||
_, err = SetFields(csvIntegrator, formPost)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get Field Value", func(t *testing.T) {
|
||||
value := Value(csvIntegrator, "input").(string)
|
||||
assert.Equal(t, string(data), value)
|
||||
})
|
||||
|
||||
t.Run("List Services from CSV File", func(t *testing.T) {
|
||||
services, err := csvIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 10, len(services))
|
||||
})
|
||||
|
||||
t.Run("Confirm Services from CSV File", func(t *testing.T) {
|
||||
services, err := csvIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "Bulk Upload", services[0].Name)
|
||||
assert.Equal(t, "http://google.com", services[0].Domain)
|
||||
assert.Equal(t, 60, services[0].Interval)
|
||||
for _, s := range services {
|
||||
t.Log(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
dTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
type dockerIntegration struct {
|
||||
*types.Integration
|
||||
}
|
||||
|
||||
var dockerIntegrator = &dockerIntegration{&types.Integration{
|
||||
ShortName: "docker",
|
||||
Name: "Docker",
|
||||
Icon: "<i class=\"fab fa-docker\"></i>",
|
||||
Description: `Import multiple services from Docker by attaching the unix socket to Statping.
|
||||
You can also do this in Docker by setting <u>-v /var/run/docker.sock:/var/run/docker.sock</u> in the Statping Docker container.
|
||||
All of the containers with open TCP/UDP ports will be listed for you to choose which services you want to add. If you running Statping inside of a container,
|
||||
this container must be attached to all networks you want to communicate with.`,
|
||||
Fields: []*types.IntegrationField{
|
||||
{
|
||||
Name: "path",
|
||||
Description: "The absolute path to the Docker unix socket",
|
||||
Type: "text",
|
||||
Value: client.DefaultDockerHost,
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Description: "Version number of Docker server",
|
||||
Type: "text",
|
||||
Value: client.DefaultVersion,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
var cli *client.Client
|
||||
|
||||
func (t *dockerIntegration) Get() *types.Integration {
|
||||
return t.Integration
|
||||
}
|
||||
|
||||
func (t *dockerIntegration) List() ([]*types.Service, error) {
|
||||
var err error
|
||||
path := Value(t, "path").(string)
|
||||
version := Value(t, "version").(string)
|
||||
os.Setenv("DOCKER_HOST", path)
|
||||
os.Setenv("DOCKER_VERSION", version)
|
||||
cli, err = client.NewEnvClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
var services []*types.Service
|
||||
|
||||
containers, err := cli.ContainerList(context.Background(), dTypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, container := range containers {
|
||||
if container.State != "running" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range container.Ports {
|
||||
if v.IP == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
service := &types.Service{
|
||||
Name: container.Names[0][1:],
|
||||
Domain: v.IP,
|
||||
Type: v.Type,
|
||||
Port: int(v.PublicPort),
|
||||
Interval: 60,
|
||||
Timeout: 2,
|
||||
}
|
||||
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
}
|
||||
return services, nil
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDockerIntegration(t *testing.T) {
|
||||
|
||||
t.Run("Set Field Value", func(t *testing.T) {
|
||||
formPost := map[string][]string{}
|
||||
formPost["path"] = []string{"unix:///var/run/docker.sock"}
|
||||
formPost["version"] = []string{"1.25"}
|
||||
_, err := SetFields(csvIntegrator, formPost)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get Field Value", func(t *testing.T) {
|
||||
path := Value(dockerIntegrator, "path").(string)
|
||||
version := Value(dockerIntegrator, "version").(string)
|
||||
assert.Equal(t, "unix:///var/run/docker.sock", path)
|
||||
assert.Equal(t, "1.25", version)
|
||||
})
|
||||
|
||||
t.Run("List Services from Docker", func(t *testing.T) {
|
||||
services, err := dockerIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 0, len(services))
|
||||
})
|
||||
|
||||
t.Run("Confirm Services from Docker", func(t *testing.T) {
|
||||
services, err := dockerIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
for _, s := range services {
|
||||
t.Log(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
Integrations []types.Integrator
|
||||
log = utils.Log.WithField("type", "integration")
|
||||
)
|
||||
|
||||
func init() {
|
||||
Integrations = append(Integrations,
|
||||
csvIntegrator,
|
||||
dockerIntegrator,
|
||||
traefikIntegrator,
|
||||
)
|
||||
}
|
||||
|
||||
func Value(intg types.Integrator, fieldName string) interface{} {
|
||||
for _, v := range intg.Get().Fields {
|
||||
if fieldName == v.Name {
|
||||
return v.Value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetFields(intg types.Integrator, data map[string][]string) (*types.Integration, error) {
|
||||
i := intg.Get()
|
||||
for _, v := range i.Fields {
|
||||
if data[v.Name] != nil {
|
||||
v.Value = data[v.Name][0]
|
||||
}
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func Find(name string) (types.Integrator, error) {
|
||||
for _, i := range Integrations {
|
||||
obj := i.Get()
|
||||
if obj.ShortName == name {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New(name + " not found")
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegrations(t *testing.T) {
|
||||
|
||||
t.Run("Collect Integrations", func(t *testing.T) {
|
||||
amount := len(Integrations)
|
||||
assert.Equal(t, 3, amount)
|
||||
})
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type traefikIntegration struct {
|
||||
*types.Integration
|
||||
}
|
||||
|
||||
var traefikIntegrator = &traefikIntegration{&types.Integration{
|
||||
ShortName: "traefik",
|
||||
Name: "Traefik",
|
||||
Icon: "<i class=\"fas fa-network-wired\"></i>",
|
||||
Description: ``,
|
||||
Fields: []*types.IntegrationField{
|
||||
{
|
||||
Name: "endpoint",
|
||||
Description: "The URL for the traefik API Endpoint",
|
||||
Type: "text",
|
||||
Value: "http://localhost:8080",
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Description: "Username for HTTP Basic Authentication",
|
||||
Type: "text",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Description: "Password for HTTP Basic Authentication",
|
||||
Type: "password",
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
func (t *traefikIntegration) Get() *types.Integration {
|
||||
return t.Integration
|
||||
}
|
||||
|
||||
func (t *traefikIntegration) List() ([]*types.Service, error) {
|
||||
var err error
|
||||
var services []*types.Service
|
||||
|
||||
endpoint := Value(t, "endpoint").(string)
|
||||
|
||||
httpServices, err := fetchMethod(endpoint, "http")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, httpServices...)
|
||||
|
||||
tcpServices, err := fetchMethod(endpoint, "tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, tcpServices...)
|
||||
|
||||
return services, err
|
||||
}
|
||||
|
||||
func fetchMethod(endpoint, method string) ([]*types.Service, error) {
|
||||
var traefikServices []traefikService
|
||||
var services []*types.Service
|
||||
d, _, err := utils.HttpRequest(endpoint+"/api/"+method+"/services", "GET", nil, []string{}, nil, 10*time.Second, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(d, &traefikServices); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, s := range traefikServices {
|
||||
log.Infoln(s)
|
||||
|
||||
for _, l := range s.LoadBalancer.Servers {
|
||||
|
||||
url, err := url.Parse(l.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &types.Service{
|
||||
Name: s.Name,
|
||||
Domain: url.Hostname(),
|
||||
Port: int(utils.ToInt(url.Port())),
|
||||
Type: method,
|
||||
Interval: 60,
|
||||
Timeout: 2,
|
||||
}
|
||||
services = append(services, service)
|
||||
|
||||
}
|
||||
}
|
||||
return services, err
|
||||
}
|
||||
|
||||
type traefikService struct {
|
||||
Status string `json:"status"`
|
||||
UsedBy []string `json:"usedBy"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
LoadBalancer struct {
|
||||
Servers []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"servers"`
|
||||
PassHostHeader bool `json:"passHostHeader"`
|
||||
} `json:"loadBalancer,omitempty"`
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package integrations
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTraefikIntegration(t *testing.T) {
|
||||
|
||||
t.Run("List Services from Traefik", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
services, err := traefikIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(services))
|
||||
})
|
||||
|
||||
t.Run("Confirm Services from Traefik", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
services, err := traefikIntegrator.List()
|
||||
require.Nil(t, err)
|
||||
for _, s := range services {
|
||||
t.Log(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
|
@ -18,7 +18,6 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -64,7 +63,7 @@ func (m *Message) Create() (int64, error) {
|
|||
m.CreatedAt = time.Now().UTC()
|
||||
db := messagesDb().Create(m)
|
||||
if db.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
return 0, db.Error
|
||||
}
|
||||
return m.Id, nil
|
||||
|
@ -80,7 +79,7 @@ func (m *Message) Delete() error {
|
|||
func (m *Message) Update() (*Message, error) {
|
||||
db := messagesDb().Update(m)
|
||||
if db.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
return nil, db.Error
|
||||
}
|
||||
return m, nil
|
||||
|
|
|
@ -25,7 +25,7 @@ var (
|
|||
)
|
||||
|
||||
func checkNotifierForm(n Notifier) error {
|
||||
notifier := asNotification(n)
|
||||
notifier := n.Select()
|
||||
for _, f := range notifier.Form {
|
||||
contains := contains(f.DbField, allowedVars)
|
||||
if !contains {
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
|
||||
package notifier
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
)
|
||||
|
||||
// OnSave will trigger a notifier when it has been saved - Notifier interface
|
||||
func OnSave(method string) {
|
||||
|
@ -34,8 +38,25 @@ func OnFailure(s *types.Service, f *types.Failure) {
|
|||
if !s.AllowNotifications.Bool {
|
||||
return
|
||||
}
|
||||
|
||||
// check if User wants to receive every Status Change
|
||||
if s.UpdateNotify {
|
||||
// send only if User hasn't been already notified about the Downtime
|
||||
if !s.UserNotified {
|
||||
s.UserNotified = true
|
||||
goto sendMessages
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sendMessages:
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(BasicEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) {
|
||||
notifier := comm.(Notifier).Select()
|
||||
log.
|
||||
WithField("trigger", "OnFailure").
|
||||
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name))
|
||||
comm.(BasicEvents).OnFailure(s, f)
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +67,18 @@ func OnSuccess(s *types.Service) {
|
|||
if !s.AllowNotifications.Bool {
|
||||
return
|
||||
}
|
||||
|
||||
// check if User wants to receive every Status Change
|
||||
if s.UpdateNotify && s.UserNotified {
|
||||
s.UserNotified = false
|
||||
}
|
||||
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(BasicEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) {
|
||||
notifier := comm.(Notifier).Select()
|
||||
log.
|
||||
WithField("trigger", "OnSuccess").
|
||||
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name))
|
||||
comm.(BasicEvents).OnSuccess(s)
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +88,9 @@ func OnSuccess(s *types.Service) {
|
|||
func OnNewService(s *types.Service) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.
|
||||
WithField("trigger", "OnNewService").
|
||||
Infoln(fmt.Sprintf("Sending new service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnNewService(s)
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +103,7 @@ func OnUpdatedService(s *types.Service) {
|
|||
}
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnUpdatedService(s)
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +116,7 @@ func OnDeletedService(s *types.Service) {
|
|||
}
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnDeletedService(s)
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +126,7 @@ func OnDeletedService(s *types.Service) {
|
|||
func OnNewUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending new user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnNewUser(u)
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +136,7 @@ func OnNewUser(u *types.User) {
|
|||
func OnUpdatedUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnUpdatedUser(u)
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +146,7 @@ func OnUpdatedUser(u *types.User) {
|
|||
func OnDeletedUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnDeletedUser(u)
|
||||
}
|
||||
}
|
||||
|
@ -117,6 +156,7 @@ func OnDeletedUser(u *types.User) {
|
|||
func OnUpdatedCore(c *types.Core) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated core notification"))
|
||||
comm.(CoreEvents).OnUpdatedCore(c)
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +184,7 @@ func OnNewNotifier(n *Notification) {
|
|||
func OnUpdatedNotifier(n *Notification) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
log.Infoln(fmt.Sprintf("Sending updated notifier for %v", n.Id))
|
||||
comm.(NotifierEvents).OnUpdatedNotifier(n)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ package notifier
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -83,7 +85,11 @@ var example = &ExampleNotifier{&Notification{
|
|||
|
||||
// init will be ran when Statping is loaded, AddNotifier will add the notifier instance to the system
|
||||
func init() {
|
||||
AddNotifier(example)
|
||||
dir = utils.Directory
|
||||
source.Assets()
|
||||
utils.InitLogs()
|
||||
injectDatabase()
|
||||
AddNotifiers(example)
|
||||
}
|
||||
|
||||
// Send is the main function to hold your notifier functionality
|
||||
|
@ -209,14 +215,14 @@ func ExampleNotification() {
|
|||
}}
|
||||
|
||||
// AddNotifier accepts a Notifier to load into the Statping Notification system
|
||||
err := AddNotifier(example)
|
||||
err := AddNotifiers(example)
|
||||
fmt.Println(err)
|
||||
// Output: <nil>
|
||||
}
|
||||
|
||||
// Add a Notifier to the AddQueue function to insert it into the system
|
||||
func ExampleAddNotifier() {
|
||||
err := AddNotifier(example)
|
||||
err := AddNotifiers(example)
|
||||
fmt.Println(err)
|
||||
// Output: <nil>
|
||||
}
|
||||
|
@ -226,7 +232,8 @@ func ExampleNotification_OnSuccess() {
|
|||
msg := fmt.Sprintf("this is a successful message as a string passing into AddQueue function")
|
||||
example.AddQueue("example", msg)
|
||||
fmt.Println(len(example.Queue))
|
||||
// Output: 1
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
// Add a new message into the queue OnSuccess
|
||||
|
@ -252,7 +259,7 @@ func ExampleOnTest() {
|
|||
func ExampleNotification_CanTest() {
|
||||
testable := example.CanTest()
|
||||
fmt.Print(testable)
|
||||
// Output: false
|
||||
// Output: true
|
||||
}
|
||||
|
||||
// Add any type of interface to the AddQueue function to be ran in the queue
|
||||
|
@ -261,7 +268,8 @@ func ExampleNotification_AddQueue() {
|
|||
example.AddQueue("example", msg)
|
||||
queue := example.Queue
|
||||
fmt.Printf("Example has %v items in the queue", len(queue))
|
||||
// Output: Example has 2 items in the queue
|
||||
// Output:
|
||||
// Example has 2 items in the queue
|
||||
}
|
||||
|
||||
// The Send method will run the main functionality of your notifier
|
||||
|
|
|
@ -33,6 +33,7 @@ var (
|
|||
// db holds the Statping database connection
|
||||
db *gorm.DB
|
||||
timezone float32
|
||||
log = utils.Log.WithField("type", "notifier")
|
||||
)
|
||||
|
||||
// Notification contains all the fields for a Statping Notifier.
|
||||
|
@ -62,7 +63,6 @@ type Notification struct {
|
|||
Delay time.Duration `gorm:"-" json:"delay,string"`
|
||||
Queue []*QueueData `gorm:"-" json:"-"`
|
||||
Running chan bool `gorm:"-" json:"-"`
|
||||
Online bool `gorm:"-" json:"online"`
|
||||
testable bool `gorm:"-" json:"testable"`
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,7 @@ func (n *Notification) AfterFind() (err error) {
|
|||
func (n *Notification) AddQueue(uid string, msg interface{}) {
|
||||
data := &QueueData{uid, msg}
|
||||
n.Queue = append(n.Queue, data)
|
||||
log.WithFields(utils.ToFields(data, n)).Infoln(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue)))
|
||||
}
|
||||
|
||||
// CanTest returns true if the notifier implements the OnTest interface
|
||||
|
@ -127,29 +128,21 @@ func asNotification(n Notifier) *Notification {
|
|||
}
|
||||
|
||||
// AddNotifier accept a Notifier interface to be added into the array
|
||||
func AddNotifier(n Notifier) error {
|
||||
if isType(n, new(Notifier)) {
|
||||
err := checkNotifierForm(n)
|
||||
if err != nil {
|
||||
return err
|
||||
func AddNotifiers(notifiers ...Notifier) error {
|
||||
for _, n := range notifiers {
|
||||
if isType(n, new(Notifier)) {
|
||||
err := checkNotifierForm(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
AllCommunications = append(AllCommunications, n)
|
||||
Init(n)
|
||||
} else {
|
||||
return errors.New("notifier does not have the required methods")
|
||||
}
|
||||
AllCommunications = append(AllCommunications, n)
|
||||
} else {
|
||||
return errors.New("notifier does not have the required methods")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load is called by core to add all the notifier into memory
|
||||
func Load() []types.AllNotifiers {
|
||||
var notifiers []types.AllNotifiers
|
||||
for _, comm := range AllCommunications {
|
||||
n := comm.(Notifier)
|
||||
Init(n)
|
||||
notifiers = append(notifiers, n)
|
||||
}
|
||||
startAllNotifiers()
|
||||
return notifiers
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeType will accept multiple interfaces and converts it into a string for logging
|
||||
|
@ -177,10 +170,9 @@ func normalizeType(ty interface{}) string {
|
|||
func (n *Notification) makeLog(msg interface{}) {
|
||||
log := &NotificationLog{
|
||||
Message: normalizeType(msg),
|
||||
Time: utils.Timestamp(time.Now()),
|
||||
Timestamp: time.Now(),
|
||||
Time: utils.Timestamp(utils.Now()),
|
||||
Timestamp: utils.Now(),
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Notifier %v has sent a message %v", n.Method, log.Message))
|
||||
n.logs = append(n.logs, log)
|
||||
}
|
||||
|
||||
|
@ -198,8 +190,8 @@ func reverseLogs(input []*NotificationLog) []*NotificationLog {
|
|||
}
|
||||
|
||||
// isInDatabase returns true if the notifier has already been installed
|
||||
func isInDatabase(n *Notification) bool {
|
||||
inDb := modelDb(n).RecordNotFound()
|
||||
func isInDatabase(n Notifier) bool {
|
||||
inDb := modelDb(n.Select()).RecordNotFound()
|
||||
return !inDb
|
||||
}
|
||||
|
||||
|
@ -225,13 +217,14 @@ func Update(n Notifier, notif *Notification) (*Notification, error) {
|
|||
}
|
||||
|
||||
// insertDatabase will create a new record into the database for the notifier
|
||||
func insertDatabase(n *Notification) (int64, error) {
|
||||
n.Limits = 3
|
||||
query := db.Create(n)
|
||||
func insertDatabase(n Notifier) (int64, error) {
|
||||
noti := n.Select()
|
||||
noti.Limits = 3
|
||||
query := db.Create(noti)
|
||||
if query.Error != nil {
|
||||
return 0, query.Error
|
||||
}
|
||||
return n.Id, query.Error
|
||||
return noti.Id, query.Error
|
||||
}
|
||||
|
||||
// SelectNotifier returns the Notification struct from the database
|
||||
|
@ -246,7 +239,7 @@ func SelectNotifier(method string) (*Notification, Notifier, error) {
|
|||
return notifier, comm.(Notifier), nil
|
||||
}
|
||||
}
|
||||
return nil, nil, nil
|
||||
return nil, nil, errors.New("cannot find notifier")
|
||||
}
|
||||
|
||||
// Init accepts the Notifier interface to initialize the notifier
|
||||
|
@ -298,7 +291,9 @@ CheckNotifier:
|
|||
msg := notification.Queue[0]
|
||||
err := n.Send(msg.Data)
|
||||
if err != nil {
|
||||
utils.Log(2, fmt.Sprintf("notifier %v had an error: %v", notification.Method, err))
|
||||
log.WithFields(utils.ToFields(notification, msg)).Warnln(fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err))
|
||||
} else {
|
||||
log.WithFields(utils.ToFields(notification, msg)).Infoln(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue)))
|
||||
}
|
||||
notification.makeLog(msg.Data)
|
||||
if len(notification.Queue) > 1 {
|
||||
|
@ -316,11 +311,14 @@ CheckNotifier:
|
|||
|
||||
// install will check the database for the notification, if its not inserted it will insert a new record for it
|
||||
func install(n Notifier) error {
|
||||
inDb := isInDatabase(n.Select())
|
||||
inDb := isInDatabase(n)
|
||||
log.WithField("installed", inDb).
|
||||
WithFields(utils.ToFields(n)).
|
||||
Debugln(fmt.Sprintf("Checking if notifier '%v' is installed: %v", n.Select().Method, inDb))
|
||||
if !inDb {
|
||||
_, err := insertDatabase(n.Select())
|
||||
_, err := insertDatabase(n)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -339,13 +337,13 @@ func (n *Notification) LastSent() time.Duration {
|
|||
|
||||
// SentLastHour returns the total amount of notifications sent in last 1 hour
|
||||
func (n *Notification) SentLastHour() int {
|
||||
since := time.Now().Add(-1 * time.Hour)
|
||||
since := utils.Now().Add(-1 * time.Hour)
|
||||
return n.SentLast(since)
|
||||
}
|
||||
|
||||
// SentLastMinute returns the total amount of notifications sent in last 1 minute
|
||||
func (n *Notification) SentLastMinute() int {
|
||||
since := time.Now().Add(-1 * time.Minute)
|
||||
since := utils.Now().Add(-1 * time.Minute)
|
||||
return n.SentLast(since)
|
||||
}
|
||||
|
||||
|
@ -467,3 +465,19 @@ func (n *Notification) IsRunning() bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ExampleService can be used for the OnTest() method for notifiers
|
||||
var ExampleService = &types.Service{
|
||||
Id: 1,
|
||||
Name: "Interpol - All The Rage Back Home",
|
||||
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
LastStatusCode: 404,
|
||||
Expected: types.NewNullString("test example"),
|
||||
LastResponse: "<html>this is an example response</html>",
|
||||
CreatedAt: utils.Now().Add(-24 * time.Hour),
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ package notifier
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
@ -56,16 +55,9 @@ var core = &types.Core{
|
|||
Name: "testing notifiers",
|
||||
}
|
||||
|
||||
func init() {
|
||||
dir = utils.Directory
|
||||
source.Assets()
|
||||
utils.InitLogs()
|
||||
injectDatabase()
|
||||
}
|
||||
|
||||
func injectDatabase() {
|
||||
utils.DeleteFile(dir + "/statup.db")
|
||||
db, _ = gorm.Open("sqlite3", dir+"/statup.db")
|
||||
utils.DeleteFile(dir + "/notifier.db")
|
||||
db, _ = gorm.Open("sqlite3", dir+"/notifier.db")
|
||||
db.CreateTable(&Notification{})
|
||||
}
|
||||
|
||||
|
@ -79,13 +71,8 @@ func TestIsBasicType(t *testing.T) {
|
|||
assert.True(t, isType(example, new(Tester)))
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
notifiers := Load()
|
||||
assert.Equal(t, 1, len(notifiers))
|
||||
}
|
||||
|
||||
func TestIsInDatabase(t *testing.T) {
|
||||
in := isInDatabase(example.Notification)
|
||||
in := isInDatabase(example)
|
||||
assert.True(t, in)
|
||||
}
|
||||
|
||||
|
@ -101,14 +88,6 @@ func TestAddQueue(t *testing.T) {
|
|||
msg := "this is a test in the queue!"
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 1, len(example.Queue))
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 2, len(example.Queue))
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 3, len(example.Queue))
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 4, len(example.Queue))
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 5, len(example.Queue))
|
||||
}
|
||||
|
||||
func TestNotification_Update(t *testing.T) {
|
||||
|
|
169
core/sample.go
169
core/sample.go
|
@ -17,8 +17,10 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -29,7 +31,7 @@ var (
|
|||
|
||||
// InsertSampleData will create the example/dummy services for a brand new Statping installation
|
||||
func InsertSampleData() error {
|
||||
utils.Log(1, "Inserting Sample Data...")
|
||||
log.Infoln("Inserting Sample Data...")
|
||||
|
||||
insertSampleGroups()
|
||||
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
|
||||
|
@ -44,6 +46,7 @@ func InsertSampleData() error {
|
|||
Order: 1,
|
||||
GroupId: 1,
|
||||
Permalink: types.NewNullString("google"),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s2 := ReturnService(&types.Service{
|
||||
|
@ -56,6 +59,7 @@ func InsertSampleData() error {
|
|||
Timeout: 20,
|
||||
Order: 2,
|
||||
Permalink: types.NewNullString("statping_github"),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s3 := ReturnService(&types.Service{
|
||||
|
@ -68,6 +72,7 @@ func InsertSampleData() error {
|
|||
Timeout: 30,
|
||||
Order: 3,
|
||||
Public: types.NewNullBool(true),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
GroupId: 2,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
@ -83,6 +88,7 @@ func InsertSampleData() error {
|
|||
Timeout: 30,
|
||||
Order: 4,
|
||||
Public: types.NewNullBool(true),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
GroupId: 2,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
|
@ -109,7 +115,7 @@ func InsertSampleData() error {
|
|||
|
||||
insertSampleIncidents()
|
||||
|
||||
utils.Log(1, "Sample data has finished importing")
|
||||
log.Infoln("Sample data has finished importing")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -186,7 +192,7 @@ func insertSampleCheckins() error {
|
|||
})
|
||||
checkin2.Update()
|
||||
|
||||
checkTime := time.Now().Add(-24 * time.Hour)
|
||||
checkTime := time.Now().UTC().Add(-24 * time.Hour)
|
||||
for i := 0; i <= 60; i++ {
|
||||
checkHit := ReturnCheckinHit(&types.CheckinHit{
|
||||
Checkin: checkin1.Id,
|
||||
|
@ -201,32 +207,35 @@ func insertSampleCheckins() error {
|
|||
|
||||
// InsertSampleHits will create a couple new hits for the sample services
|
||||
func InsertSampleHits() error {
|
||||
|
||||
tx := hitsDB().Begin()
|
||||
sg := new(sync.WaitGroup)
|
||||
for i := int64(1); i <= 5; i++ {
|
||||
|
||||
sg.Add(1)
|
||||
service := SelectService(i)
|
||||
seed := time.Now().UnixNano()
|
||||
|
||||
utils.Log(1, fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
|
||||
log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
|
||||
createdAt := sampleStart
|
||||
|
||||
p := utils.NewPerlin(2., 2., 10, seed)
|
||||
|
||||
for hi := 0.; hi <= float64(SampleHits); hi++ {
|
||||
|
||||
latency := p.Noise1D(hi / 500)
|
||||
createdAt = createdAt.Add(60 * time.Second)
|
||||
hit := &types.Hit{
|
||||
Service: service.Id,
|
||||
CreatedAt: createdAt,
|
||||
Latency: latency,
|
||||
go func() {
|
||||
defer sg.Done()
|
||||
for hi := 0.; hi <= float64(SampleHits); hi++ {
|
||||
latency := p.Noise1D(hi / 500)
|
||||
createdAt = createdAt.Add(60 * time.Second)
|
||||
hit := &types.Hit{
|
||||
Service: service.Id,
|
||||
CreatedAt: createdAt,
|
||||
Latency: latency,
|
||||
}
|
||||
tx = tx.Create(&hit)
|
||||
}
|
||||
service.CreateHit(hit)
|
||||
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
sg.Wait()
|
||||
err := tx.Commit().Error
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// insertSampleCore will create a new Core for the seed
|
||||
|
@ -238,7 +247,7 @@ func insertSampleCore() error {
|
|||
ApiSecret: "samplesecret",
|
||||
Domain: "http://localhost:8080",
|
||||
Version: "test",
|
||||
CreatedAt: time.Now(),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UseCdn: types.NewNullBool(false),
|
||||
}
|
||||
query := coreDB().Create(core)
|
||||
|
@ -271,8 +280,8 @@ func insertMessages() error {
|
|||
Title: "Routine Downtime",
|
||||
Description: "This is an example a upcoming message for a service!",
|
||||
ServiceId: 1,
|
||||
StartOn: time.Now().Add(15 * time.Minute),
|
||||
EndOn: time.Now().Add(2 * time.Hour),
|
||||
StartOn: time.Now().UTC().Add(15 * time.Minute),
|
||||
EndOn: time.Now().UTC().Add(2 * time.Hour),
|
||||
})
|
||||
if _, err := m1.Create(); err != nil {
|
||||
return err
|
||||
|
@ -307,7 +316,7 @@ func InsertLargeSampleData() error {
|
|||
if err := insertMessages(); err != nil {
|
||||
return err
|
||||
}
|
||||
createdOn := time.Now().Add((-24 * 90) * time.Hour).UTC()
|
||||
createdOn := time.Now().UTC().Add((-24 * 90) * time.Hour)
|
||||
s6 := ReturnService(&types.Service{
|
||||
Name: "JSON Lint",
|
||||
Domain: "https://jsonlint.com",
|
||||
|
@ -438,7 +447,7 @@ func InsertLargeSampleData() error {
|
|||
s14.Create(false)
|
||||
s15.Create(false)
|
||||
|
||||
var dayAgo = time.Now().Add((-24 * 90) * time.Hour)
|
||||
var dayAgo = time.Now().UTC().Add((-24 * 90) * time.Hour)
|
||||
|
||||
insertHitRecords(dayAgo, 5450)
|
||||
|
||||
|
@ -451,17 +460,17 @@ func InsertLargeSampleData() error {
|
|||
func insertFailureRecords(since time.Time, amount int64) {
|
||||
for i := int64(14); i <= 15; i++ {
|
||||
service := SelectService(i)
|
||||
utils.Log(1, fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
|
||||
log.Infoln(fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
|
||||
createdAt := since
|
||||
|
||||
for fi := int64(1); fi <= amount; fi++ {
|
||||
createdAt = createdAt.Add(2 * time.Minute)
|
||||
|
||||
failure := &Failure{&types.Failure{
|
||||
failure := &types.Failure{
|
||||
Service: service.Id,
|
||||
Issue: "testing right here",
|
||||
CreatedAt: createdAt,
|
||||
}}
|
||||
}
|
||||
|
||||
service.CreateFailure(failure)
|
||||
}
|
||||
|
@ -472,7 +481,7 @@ func insertFailureRecords(since time.Time, amount int64) {
|
|||
func insertHitRecords(since time.Time, amount int64) {
|
||||
for i := int64(1); i <= 15; i++ {
|
||||
service := SelectService(i)
|
||||
utils.Log(1, fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
|
||||
log.Infoln(fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
|
||||
createdAt := since
|
||||
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano())
|
||||
for hi := int64(1); hi <= amount; hi++ {
|
||||
|
@ -480,7 +489,7 @@ func insertHitRecords(since time.Time, amount int64) {
|
|||
createdAt = createdAt.Add(1 * time.Minute)
|
||||
hit := &types.Hit{
|
||||
Service: service.Id,
|
||||
CreatedAt: createdAt,
|
||||
CreatedAt: createdAt.UTC(),
|
||||
Latency: latency,
|
||||
}
|
||||
service.CreateHit(hit)
|
||||
|
@ -489,3 +498,99 @@ func insertHitRecords(since time.Time, amount int64) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// TmpRecords is used for testing Statping. It will create a SQLite database file
|
||||
// with sample data and store it in the /tmp folder to be used by the tests.
|
||||
func TmpRecords(dbFile string) error {
|
||||
var sqlFile = utils.Directory + "/" + dbFile
|
||||
utils.CreateDirectory(utils.Directory + "/tmp")
|
||||
var tmpSqlFile = utils.Directory + "/tmp/" + types.SqliteFilename
|
||||
SampleHits = 480
|
||||
|
||||
var err error
|
||||
CoreApp = NewCore()
|
||||
CoreApp.Name = "Tester"
|
||||
configs := &types.DbConfig{
|
||||
DbConn: "sqlite",
|
||||
Project: "Tester",
|
||||
Location: utils.Directory,
|
||||
SqlFile: sqlFile,
|
||||
}
|
||||
log.Infoln("saving config.yml in: " + utils.Directory)
|
||||
if configs, err = CoreApp.SaveConfig(configs); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading config.yml from: " + utils.Directory)
|
||||
if configs, err = LoadConfigFile(utils.Directory); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("connecting to database")
|
||||
|
||||
exists := utils.FileExists(tmpSqlFile)
|
||||
if exists {
|
||||
log.Infoln(tmpSqlFile + " was found, copying the temp database to " + sqlFile)
|
||||
if err := utils.DeleteFile(sqlFile); err != nil {
|
||||
log.Infoln(sqlFile + " was not found")
|
||||
}
|
||||
if err := utils.CopyFile(tmpSqlFile, sqlFile); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading config.yml from: " + utils.Directory)
|
||||
|
||||
if err := CoreApp.Connect(false, utils.Directory); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("selecting the Core variable")
|
||||
if _, err := SelectCore(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("inserting notifiers into database")
|
||||
if err := InsertNotifierDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading all services")
|
||||
if _, err := CoreApp.SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := AttachNotifiers(); err != nil {
|
||||
return err
|
||||
}
|
||||
CoreApp.Notifications = notifier.AllCommunications
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infoln(tmpSqlFile + " not found, creating a new database...")
|
||||
|
||||
if err := CoreApp.Connect(false, utils.Directory); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("creating database")
|
||||
if err := CoreApp.CreateDatabase(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("migrating database")
|
||||
if err := CoreApp.MigrateDatabase(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("insert large sample data into database")
|
||||
if err := InsertLargeSampleData(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("selecting the Core variable")
|
||||
if CoreApp, err = SelectCore(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("inserting notifiers into database")
|
||||
if err := InsertNotifierDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("loading all services")
|
||||
if _, err := CoreApp.SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("copying sql database file to: " + tmpSqlFile)
|
||||
if err := utils.CopyFile(sqlFile, tmpSqlFile); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
|
|||
var services []*Service
|
||||
db := servicesDB().Find(&services).Order("order_id desc")
|
||||
if db.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("service error: %v", db.Error))
|
||||
log.Errorln(fmt.Sprintf("service error: %v", db.Error))
|
||||
return nil, db.Error
|
||||
}
|
||||
CoreApp.Services = nil
|
||||
|
@ -144,7 +144,7 @@ func (s *Service) AvgTime() string {
|
|||
|
||||
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
|
||||
func (s *Service) OnlineDaysPercent(days int) float32 {
|
||||
ago := time.Now().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
return s.OnlineSince(ago)
|
||||
}
|
||||
|
||||
|
@ -207,7 +207,7 @@ func (s *Service) SmallText() string {
|
|||
}
|
||||
if len(last) > 0 {
|
||||
lastFailure := s.lastFailure()
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().Add(s.Downtime()), time.Now())
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().UTC().Add(s.Downtime()), time.Now().UTC())
|
||||
return fmt.Sprintf("Reported offline %v, %v", got, lastFailure.ParseError())
|
||||
} else {
|
||||
return fmt.Sprintf("%v is currently offline", s.Name)
|
||||
|
@ -240,7 +240,7 @@ func Dbtimestamp(group string, column string) string {
|
|||
default:
|
||||
seconds = 60
|
||||
}
|
||||
switch CoreApp.DbConnection {
|
||||
switch CoreApp.Config.DbConn {
|
||||
case "mysql":
|
||||
return fmt.Sprintf("CONCAT(date_format(created_at, '%%Y-%%m-%%d %%H:00:00')) AS timeframe, AVG(%v) AS value", column)
|
||||
case "postgres":
|
||||
|
@ -260,7 +260,7 @@ func (s *Service) Downtime() time.Duration {
|
|||
if len(hits) == 0 {
|
||||
return time.Now().UTC().Sub(fail.CreatedAt.UTC())
|
||||
}
|
||||
since := fail.CreatedAt.UTC().Sub(fail.CreatedAt.UTC())
|
||||
since := fail.CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
|
||||
return since
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
|
|||
model = model.Order("timeframe asc", false).Group("timeframe")
|
||||
rows, err := model.Rows()
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Errorf("issue fetching service chart data: %v", err))
|
||||
log.Errorln(fmt.Errorf("issue fetching service chart data: %v", err))
|
||||
}
|
||||
for rows.Next() {
|
||||
var gd DateScan
|
||||
|
@ -281,10 +281,10 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
|
|||
var createdTime time.Time
|
||||
var err error
|
||||
rows.Scan(&createdAt, &value)
|
||||
if CoreApp.DbConnection == "postgres" {
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
createdTime, err = time.Parse(types.TIME_NANO, createdAt)
|
||||
if err != nil {
|
||||
utils.Log(4, fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
|
||||
log.Errorln(fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
|
||||
}
|
||||
} else {
|
||||
createdTime, err = time.Parse(types.TIME, createdAt)
|
||||
|
@ -301,7 +301,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
|
|||
func (d *DateScanObj) ToString() string {
|
||||
data, err := json.Marshal(d.Array)
|
||||
if err != nil {
|
||||
utils.Log(2, err)
|
||||
log.Warnln(err)
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
|
@ -309,7 +309,7 @@ func (d *DateScanObj) ToString() string {
|
|||
|
||||
// AvgUptime24 returns a service's average online status for last 24 hours
|
||||
func (s *Service) AvgUptime24() string {
|
||||
ago := time.Now().Add(-24 * time.Hour)
|
||||
ago := time.Now().UTC().Add(-24 * time.Hour)
|
||||
return s.AvgUptime(ago)
|
||||
}
|
||||
|
||||
|
@ -371,7 +371,7 @@ func (s *Service) Delete() error {
|
|||
i := s.index()
|
||||
err := servicesDB().Delete(s)
|
||||
if err.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
|
||||
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
|
||||
return err.Error
|
||||
}
|
||||
s.Close()
|
||||
|
@ -386,7 +386,7 @@ func (s *Service) Delete() error {
|
|||
func (s *Service) Update(restart bool) error {
|
||||
err := servicesDB().Update(&s)
|
||||
if err.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
|
||||
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
|
||||
return err.Error
|
||||
}
|
||||
// clear the notification queue for a service
|
||||
|
@ -410,10 +410,10 @@ func (s *Service) Update(restart bool) error {
|
|||
|
||||
// Create will create a service and insert it into the database
|
||||
func (s *Service) Create(check bool) (int64, error) {
|
||||
s.CreatedAt = time.Now()
|
||||
s.CreatedAt = time.Now().UTC()
|
||||
db := servicesDB().Create(s)
|
||||
if db.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
|
||||
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
|
||||
return 0, db.Error
|
||||
}
|
||||
s.Start()
|
||||
|
|
|
@ -17,6 +17,7 @@ package core
|
|||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -24,7 +25,6 @@ import (
|
|||
|
||||
var (
|
||||
newServiceId int64
|
||||
newGroupId int64
|
||||
)
|
||||
|
||||
func TestSelectHTTPService(t *testing.T) {
|
||||
|
@ -64,18 +64,15 @@ func TestSelectTCPService(t *testing.T) {
|
|||
|
||||
func TestUpdateService(t *testing.T) {
|
||||
service := SelectService(1)
|
||||
service2 := SelectService(2)
|
||||
assert.Equal(t, "Google", service.Name)
|
||||
assert.Equal(t, "Statping Github", service2.Name)
|
||||
assert.True(t, service.Online)
|
||||
assert.True(t, service2.Online)
|
||||
service.Name = "Updated Google"
|
||||
service.Interval = 5
|
||||
err := service.Update(true)
|
||||
assert.Nil(t, err)
|
||||
// check if updating pointer array shutdown any other service
|
||||
service2 = SelectService(2)
|
||||
assert.True(t, service2.Online)
|
||||
service = SelectService(1)
|
||||
assert.Equal(t, "Updated Google", service.Name)
|
||||
assert.Equal(t, 5, service.Interval)
|
||||
}
|
||||
|
||||
func TestUpdateAllServices(t *testing.T) {
|
||||
|
@ -121,7 +118,7 @@ func TestCheckTCPService(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServiceOnline24Hours(t *testing.T) {
|
||||
since := time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
service := SelectService(1)
|
||||
assert.Equal(t, float32(100), service.OnlineSince(since))
|
||||
service2 := SelectService(5)
|
||||
|
@ -137,7 +134,7 @@ func TestServiceSmallText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServiceAvgUptime(t *testing.T) {
|
||||
since := time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
service := SelectService(1)
|
||||
assert.NotEqual(t, "0.00", service.AvgUptime(since))
|
||||
service2 := SelectService(5)
|
||||
|
@ -259,10 +256,10 @@ func TestServiceFailedTCPCheck(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateServiceFailure(t *testing.T) {
|
||||
fail := &Failure{&types.Failure{
|
||||
fail := &types.Failure{
|
||||
Issue: "This is not an issue, but it would container HTTP response errors.",
|
||||
Method: "http",
|
||||
}}
|
||||
}
|
||||
service := SelectService(8)
|
||||
id, err := service.CreateFailure(fail)
|
||||
assert.Nil(t, err)
|
||||
|
@ -353,13 +350,13 @@ func TestSelectServiceLink(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDbtimestamp(t *testing.T) {
|
||||
CoreApp.DbConnection = "mysql"
|
||||
CoreApp.Config.DbConn = "mysql"
|
||||
query := Dbtimestamp("minute", "latency")
|
||||
assert.Equal(t, "CONCAT(date_format(created_at, '%Y-%m-%d %H:00:00')) AS timeframe, AVG(latency) AS value", query)
|
||||
CoreApp.DbConnection = "postgres"
|
||||
CoreApp.Config.DbConn = "postgres"
|
||||
query = Dbtimestamp("minute", "latency")
|
||||
assert.Equal(t, "date_trunc('minute', created_at) AS timeframe, AVG(latency) AS value", query)
|
||||
CoreApp.DbConnection = "sqlite"
|
||||
CoreApp.Config.DbConn = "sqlite"
|
||||
query = Dbtimestamp("minute", "latency")
|
||||
assert.Equal(t, "datetime((strftime('%s', created_at) / 60) * 60, 'unixepoch') AS timeframe, AVG(latency) as value", query)
|
||||
}
|
||||
|
@ -401,7 +398,7 @@ func TestService_TotalFailures24(t *testing.T) {
|
|||
|
||||
func TestService_TotalFailuresOnDate(t *testing.T) {
|
||||
t.SkipNow()
|
||||
ago := time.Now().UTC()
|
||||
ago := utils.Now().UTC()
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailuresOnDate(ago)
|
||||
assert.Nil(t, err)
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
// SparklineDayFailures returns a string array of daily service failures
|
||||
func (s *Service) SparklineDayFailures(days int) string {
|
||||
var arr []string
|
||||
ago := time.Now().Add((time.Duration(days) * -24) * time.Hour)
|
||||
ago := time.Now().UTC().Add((time.Duration(days) * -24) * time.Hour)
|
||||
for day := 1; day <= days; day++ {
|
||||
ago = ago.Add(24 * time.Hour)
|
||||
failures, _ := s.TotalFailuresOnDate(ago)
|
||||
|
|
|
@ -68,7 +68,7 @@ func (u *User) Update() error {
|
|||
|
||||
// Create will insert a new User into the database
|
||||
func (u *User) Create() (int64, error) {
|
||||
u.CreatedAt = time.Now()
|
||||
u.CreatedAt = time.Now().UTC()
|
||||
u.Password = utils.HashPassword(u.Password)
|
||||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
|
@ -77,7 +77,7 @@ func (u *User) Create() (int64, error) {
|
|||
return 0, db.Error
|
||||
}
|
||||
if u.Id == 0 {
|
||||
utils.Log(3, fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
|
||||
log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
|
||||
return 0, db.Error
|
||||
}
|
||||
return u.Id, db.Error
|
||||
|
@ -88,7 +88,7 @@ func SelectAllUsers() ([]*User, error) {
|
|||
var users []*User
|
||||
db := usersDB().Find(&users)
|
||||
if db.Error != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to load all users. %v", db.Error))
|
||||
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error))
|
||||
return nil, db.Error
|
||||
}
|
||||
return users, db.Error
|
||||
|
@ -99,7 +99,7 @@ func SelectAllUsers() ([]*User, error) {
|
|||
func AuthUser(username, password string) (*User, bool) {
|
||||
user, err := SelectUsername(username)
|
||||
if err != nil {
|
||||
utils.Log(2, fmt.Errorf("user %v not found", username))
|
||||
log.Warnln(fmt.Errorf("user %v not found", username))
|
||||
return nil, false
|
||||
}
|
||||
if CheckHash(password, user.Password) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,15 +1,12 @@
|
|||
FROM golang:1.11-alpine as base
|
||||
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
|
||||
FROM golang:1.13.5-alpine as base
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
ARG VERSION
|
||||
ENV DEP_VERSION v0.5.0
|
||||
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq
|
||||
RUN curl -L -s https://github.com/golang/dep/releases/download/$DEP_VERSION/dep-linux-amd64 -o /go/bin/dep && \
|
||||
chmod +x /go/bin/dep
|
||||
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
|
||||
chmod +x /usr/local/bin/sass
|
||||
WORKDIR /go/src/github.com/hunterlong/statping
|
||||
ADD . /go/src/github.com/hunterlong/statping
|
||||
RUN make dep
|
||||
RUN go mod vendor
|
||||
RUN make dev-deps
|
||||
RUN make install
|
||||
|
||||
|
@ -17,4 +14,4 @@ ENV IS_DOCKER=true
|
|||
ENV STATPING_DIR=/app
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["statping"]
|
||||
CMD ["statping"]
|
||||
|
|
|
@ -2496,7 +2496,7 @@ for application logging
|
|||
```go
|
||||
func Log(level int, err interface{}) error
|
||||
```
|
||||
Log creates a new entry in the Logger. Log has 1-5 levels depending on how
|
||||
Log creates a new entry in the utils.Log. Log has 1-5 levels depending on how
|
||||
critical the log/error is
|
||||
|
||||
#### func NewSHA1Hash
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: nginx:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
ports:
|
||||
- 80:80
|
||||
expose:
|
||||
- 80
|
||||
|
||||
statping:
|
||||
container_name: statping
|
||||
image: hunterlong/statping:latest
|
||||
restart: always
|
||||
expose:
|
||||
- 8080
|
|
@ -0,0 +1,12 @@
|
|||
worker_processes 1;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
server {
|
||||
location / {
|
||||
proxy_pass http://statping:8080;
|
||||
}
|
||||
listen 80;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"projectId": "bi8mhr",
|
||||
"env": {
|
||||
"DB_HOST": "localhost",
|
||||
"DB_USER": "root",
|
||||
"DB_DATABASE": "root",
|
||||
"DB_PORT": "5432",
|
||||
"DB_PASS": "password123",
|
||||
"GO_ENV": "production"
|
||||
},
|
||||
"chromeWebSecurity": false,
|
||||
"defaultCommandTimeout": 5000,
|
||||
"requestTimeout": 5000,
|
||||
"watchForFileChanges": false
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
context('Setup Process', () => {
|
||||
|
||||
// it('should go to setup Statping with Postgres', () => {
|
||||
// cy.visit('http://localhost:8080')
|
||||
// cy.get('select[name=db_connection]').select('postgres')
|
||||
// cy.get('input[name="db_host"]').clear().type(Cypress.env('DB_HOST'))
|
||||
// cy.get('input[name="db_port"]').clear().type('5432')
|
||||
// cy.get('input[name="db_user"]').clear().type(Cypress.env('DB_USER'))
|
||||
// if (Cypress.env('TRAVIS')==="yes") {
|
||||
// cy.get('input[name="db_password"]').clear()
|
||||
// } else {
|
||||
// cy.get('input[name="db_password"]').clear().type(Cypress.env('DB_PASS'))
|
||||
// }
|
||||
// cy.get('input[name="db_database"]').clear().type(Cypress.env('DB_DATABASE'))
|
||||
// cy.get('input[name="project"]').clear().type('Demo Tester')
|
||||
// cy.get('input[name="description"]').clear().type('This is a test from Crypress!')
|
||||
// cy.get('input[name="domain"]').clear().type('http://localhost:8080')
|
||||
// cy.get('input[name="username"]').clear().type('admin')
|
||||
// cy.get('input[name="email"]').clear().type('info@domain.com')
|
||||
// cy.get('input[name="password"]').clear().type('admin')
|
||||
// cy.scrollTo('bottom')
|
||||
// cy.get('#setup_button').click().wait(10000)
|
||||
// cy.get('.header-title').should('contain', 'Demo Tester')
|
||||
// cy.get('.header-desc').should('contain', 'This is a test from Crypress!')
|
||||
// cy.scrollTo('bottom')
|
||||
// cy.get('.service_li').should('have.length', 5)
|
||||
// cy.get('.card').should('have.length', 5)
|
||||
// })
|
||||
|
||||
it('should go to setup Statping with SQLite', () => {
|
||||
cy.visit('http://localhost:8080')
|
||||
cy.get('select[name=db_connection]').select('sqlite')
|
||||
cy.get('input[name="project"]').clear().type('Demo Tester')
|
||||
cy.get('input[name="description"]').clear().type('This is a test from Crypress!')
|
||||
cy.get('input[name="domain"]').clear().type('http://localhost:8080')
|
||||
cy.get('input[name="username"]').clear().type('admin')
|
||||
cy.get('input[name="email"]').clear().type('info@domain.com')
|
||||
cy.get('input[name="password"]').clear().type('admin')
|
||||
cy.scrollTo('bottom')
|
||||
cy.get('#setup_button').click()
|
||||
cy.get('.header-title').should('contain', 'Demo Tester')
|
||||
cy.get('.header-desc').should('contain', 'This is a test from Crypress!')
|
||||
cy.scrollTo('bottom')
|
||||
cy.get('.service_li').should('have.length', 5)
|
||||
cy.get('.card').should('have.length', 5)
|
||||
})
|
||||
|
||||
})
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
context('Asset Tests', () => {
|
||||
|
||||
beforeEach(function () {
|
||||
cy.visit('http://localhost:8080/dashboard')
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('admin')
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
it('should create local assets', () => {
|
||||
cy.visit('http://localhost:8080/settings/build')
|
||||
cy.get('#v-pills-style-tab').click()
|
||||
cy.wait(500)
|
||||
cy.get(':nth-child(2) > .CodeMirror-line').should('contain', '$background-color')
|
||||
})
|
||||
|
||||
it('should save assets form', () => {
|
||||
cy.request({method: 'POST', url: 'http://localhost:8080/settings/css', form: true, body: {
|
||||
variables: '$tester: #bababa',
|
||||
theme: '@import \'variables\'; .test-var { color: $tester; }'
|
||||
}})
|
||||
})
|
||||
|
||||
it('should confirm sass variable in css', () => {
|
||||
cy.request('http://localhost:8080/css/base.css').its('body').should('contain', '.test-var')
|
||||
})
|
||||
|
||||
it('should delete assets', () => {
|
||||
cy.visit('http://localhost:8080/settings')
|
||||
cy.get('#v-pills-style-tab').click()
|
||||
cy.wait(500)
|
||||
cy.get('.btn-danger').click()
|
||||
})
|
||||
|
||||
it('should check css file after delete', () => {
|
||||
cy.request('http://localhost:8080/css/base.css').its('body').should('contain', 'BODY')
|
||||
})
|
||||
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
context('Dashboard Tests', () => {
|
||||
|
||||
beforeEach(function() {
|
||||
cy.visit('http://localhost:8080/dashboard')
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('admin')
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
it('should view logs', () => {
|
||||
cy.visit('http://localhost:8080/settings')
|
||||
cy.get(':nth-child(5) > .nav-link').click()
|
||||
cy.wait(10000)
|
||||
cy.get('#live_logs').should('contain', 'Service')
|
||||
})
|
||||
|
||||
it('should view help', () => {
|
||||
cy.visit('http://localhost:8080/settings')
|
||||
cy.get(':nth-child(6) > .nav-link').click()
|
||||
cy.title().should('eq', 'Statping | Help')
|
||||
cy.get('.col-12 > :nth-child(1)').should('contain', 'Statping')
|
||||
})
|
||||
|
||||
});
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
context('Service Tests', () => {
|
||||
|
||||
beforeEach(function () {
|
||||
cy.visit('http://localhost:8080/dashboard')
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('admin')
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
it('should view services', () => {
|
||||
cy.visit('http://localhost:8080/services')
|
||||
cy.get('tr').should('have.length', 6)
|
||||
cy.title().should('eq', 'Statping | Services')
|
||||
})
|
||||
|
||||
it('should create HTTP GET service', () => {
|
||||
cy.visit('http://localhost:8080/services')
|
||||
cy.get('select[name="method"]').select('GET')
|
||||
cy.get('input[name="name"]').clear().type('Google.com')
|
||||
cy.get('select[name="check_type"]').select('http')
|
||||
cy.get('input[name="domain"]').clear().type('https://google.com')
|
||||
cy.get('input[name="expected_status"]').clear().type('200')
|
||||
cy.get('input[name="interval"]').clear().type('25')
|
||||
cy.get('input[name="timeout"]').clear().type('30')
|
||||
cy.get('form').submit()
|
||||
cy.title().should('eq', 'Statping | Services')
|
||||
cy.get('tr').should('have.length', 7)
|
||||
})
|
||||
|
||||
it('should create HTTP POST service', () => {
|
||||
cy.visit('http://localhost:8080/services')
|
||||
cy.get('select[name="method"]').select('POST')
|
||||
cy.get('input[name="name"]').clear().type('JSON Regex Test')
|
||||
cy.get('select[name="check_type"]').select('http')
|
||||
cy.get('input[name="domain"]').clear().type('https://jsonplaceholder.typicode.com/posts')
|
||||
cy.get('textarea[name="post_data"]').clear().type(`(title)": "((\\"|[statping])*)"`)
|
||||
cy.get('input[name="expected_status"]').clear().type('201')
|
||||
cy.get('input[name="interval"]').clear().type('15')
|
||||
cy.get('input[name="timeout"]').clear().type('45')
|
||||
cy.get('form').submit()
|
||||
cy.title().should('eq', 'Statping | Services')
|
||||
cy.get('tr').should('have.length', 8)
|
||||
})
|
||||
|
||||
it('should create TCP service', () => {
|
||||
cy.visit('http://localhost:8080/services')
|
||||
cy.get('select[name="check_type"]').select('tcp')
|
||||
cy.get('input[name="name"]').clear().type('Google DNS')
|
||||
cy.get('input[name="domain"]').clear().type('8.8.8.8')
|
||||
cy.get('input[name="port"]').clear().type('53')
|
||||
cy.get('input[name="interval"]').clear().type('25')
|
||||
cy.get('input[name="timeout"]').clear().type('15')
|
||||
cy.get('form').submit()
|
||||
cy.title().should('eq', 'Statping | Services')
|
||||
cy.get('tr').should('have.length', 9)
|
||||
})
|
||||
|
||||
it('should view HTTP GET service', () => {
|
||||
cy.visit('http://localhost:8080/service/6')
|
||||
cy.title().should('eq', 'Statping | Google.com Service')
|
||||
})
|
||||
|
||||
it('should view HTTP POST service', () => {
|
||||
cy.visit('http://localhost:8080/service/7')
|
||||
cy.title().should('eq', 'Statping | JSON Regex Test Service')
|
||||
})
|
||||
|
||||
it('should view TCP service', () => {
|
||||
cy.visit('http://localhost:8080/service/8')
|
||||
cy.title().should('eq', 'Statping | Google DNS Service')
|
||||
})
|
||||
|
||||
it('should update HTTP service', () => {
|
||||
cy.visit('http://localhost:8080/service/6')
|
||||
cy.title().should('eq', 'Statping | Google.com Service')
|
||||
cy.get('#service_name').clear().type('Google Updated')
|
||||
cy.get('#service_interval').clear().type('60')
|
||||
cy.get(':nth-child(3) > form').submit()
|
||||
cy.title().should('eq', 'Statping | Google Updated Service')
|
||||
cy.get('#service_name').should('have.value', 'Google Updated')
|
||||
});
|
||||
|
||||
it('should check the updated service', () => {
|
||||
cy.visit('http://localhost:8080/service/6')
|
||||
cy.title().should('eq', 'Statping | Google Updated Service')
|
||||
cy.get('#service_name').should('have.value', 'Google Updated')
|
||||
cy.get('#service_interval').should('have.value', '60')
|
||||
})
|
||||
|
||||
it('should delete a service', () => {
|
||||
cy.visit('http://localhost:8080/services')
|
||||
cy.get(':nth-child(5) > .text-right > .btn-group > .btn-danger').click()
|
||||
cy.title().should('eq', 'Statping | Services')
|
||||
cy.get('tr').should('have.length', 8)
|
||||
})
|
||||
|
||||
|
||||
});
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
context('Settings Forms', () => {
|
||||
|
||||
beforeEach(function() {
|
||||
cy.visit('http://localhost:8080/dashboard')
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('admin')
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
it('should edit main settings', () => {
|
||||
cy.visit('http://localhost:8080/settings')
|
||||
cy.get('input[name="project"]').clear().type('Project Updated')
|
||||
cy.get('input[name="description"]').clear().type('This is an awesome page')
|
||||
cy.get('input[name="domain"]').clear().type('http://0.0.0.0:8080')
|
||||
cy.get('textarea[name="footer"]').clear().type('This is a custom footer')
|
||||
cy.get('#v-pills-home > form').submit()
|
||||
cy.title().should('eq', 'Statping | Settings')
|
||||
cy.get('input[name="project"]').should('have.value', 'Project Updated')
|
||||
cy.get('input[name="description"]').should('have.value', 'This is an awesome page')
|
||||
cy.get('input[name="domain"]').should('have.value', 'http://0.0.0.0:8080')
|
||||
cy.get('.footer').should('contain', 'This is a custom footer')
|
||||
})
|
||||
|
||||
// it('should check index page for changes', () => {
|
||||
// cy.visit('http://localhost:8080/')
|
||||
// cy.title().should('eq', 'Project Updated Status')
|
||||
// cy.get('.header-title').should('contain', 'Project Updated')
|
||||
// cy.get('.header-desc').should('contain', 'This is an awesome page')
|
||||
// })
|
||||
|
||||
});
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
context('User Testing', () => {
|
||||
|
||||
beforeEach(function () {
|
||||
cy.visit('http://localhost:8080/dashboard')
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('admin')
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
it('should view users', () => {
|
||||
cy.visit('http://localhost:8080/users')
|
||||
cy.get('tr').should('have.length', 2)
|
||||
cy.title().should('eq', 'Statping | Users')
|
||||
})
|
||||
|
||||
it('should create a new user', () => {
|
||||
cy.visit('http://localhost:8080/users')
|
||||
cy.get('input[name="username"]').type('hunterlong')
|
||||
cy.get('input[name="email"]').type('info@yayaya.com')
|
||||
cy.get('input[name="password"]').type('admin')
|
||||
cy.get('input[name="password_confirm"]').type('admin')
|
||||
cy.get('form').submit()
|
||||
cy.get('tr').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('should create a edit user', () => {
|
||||
cy.visit('http://localhost:8080/user/2')
|
||||
cy.get('input[name="password"]').type('password567')
|
||||
cy.get('input[name="password_confirm"]').type('password567')
|
||||
cy.get('form').submit()
|
||||
cy.get('tr').should('have.length', 3)
|
||||
})
|
||||
|
||||
// it('should logout and login with new password', () => {
|
||||
// cy.visit('http://localhost:8080/logout')
|
||||
// cy.title().should('eq', 'Statping | Users')
|
||||
// cy.get('#user_2 > .btn-group > .btn-danger').click()
|
||||
// cy.get('tr').should('have.length', 2)
|
||||
// cy.visit('http://localhost:8080/login')
|
||||
// cy.get('input[name="username"]').type('hunterlong')
|
||||
// cy.get('input[name="password"]').type('password567')
|
||||
// cy.get('form').submit()
|
||||
// cy.title().should('eq', 'Project Updated Status')
|
||||
// })
|
||||
|
||||
it('should delete a user', () => {
|
||||
cy.visit('http://localhost:8080/users')
|
||||
cy.get('#user_2 > .btn-group > .btn-danger').click()
|
||||
cy.get('tr').should('have.length', 2)
|
||||
})
|
||||
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
File diff suppressed because it is too large
Load Diff
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "statping-testing",
|
||||
"version": "1.0.0",
|
||||
"description": "Statping Application Testing using Cypress!",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"cypress": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"start-server-and-test": "^1.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "curl -I -X GET http://localhost:8080/robots.txt",
|
||||
"cy:run-video": "cypress run --record --key $CYPRESS_KEY",
|
||||
"cy:run": "cypress run",
|
||||
"test": "bash -c \"./test.sh\"",
|
||||
"test-docker": "bash -c \"./test-docker.sh\"",
|
||||
"testnovid": "cypress run",
|
||||
"open": "cypress open"
|
||||
},
|
||||
"author": "Hunter Long",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
RELEASE_MASTER='{ "request": { "branch": "master", "config": { "script": "make docker-run-test", "services": ["docker"], "before_script": [], "after_deploy": [], "after_success": ["make publish-homebrew", "make publish-latest"], "deploy": [], "before_deploy": [], "env": { "VERSION": "$(VERSION)" } } } }'
|
||||
|
||||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(RELEASE_MASTER) https://api.travis-ci.com/repo/hunterlong%2Fstatping/requests
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
statping > /dev/null &
|
||||
|
||||
./node_modules/.bin/start-server-and-test start http://localhost:8080/robots.txt cy:run
|
|
@ -1,13 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
DIR=`pwd`
|
||||
DOCKER=`which docker`
|
||||
|
||||
$DOCKER build -t hunterlong/statping:dev -f ../Dockerfile ../../
|
||||
$DOCKER run -it -d -p 8080:8080 -v $DIR/app:/app --name statping_dev hunterlong/statping:dev
|
||||
|
||||
./node_modules/.bin/start-server-and-test start http://localhost:8080/robots.txt cy:run
|
||||
|
||||
$DOCKER stop statping_dev || true && $DOCKER rm -f statping_dev || true
|
||||
|
||||
sudo rm -rf $DIR/app
|
4
doc.go
4
doc.go
|
@ -1,4 +1,4 @@
|
|||
// Package statping is a server monitoring application that includs a status page server. Visit the Statping repo at
|
||||
// Package statping is a server monitoring application that includes a status page server. Visit the Statping repo at
|
||||
// https://github.com/hunterlong/statping to get a full understanding of what this application can do.
|
||||
//
|
||||
// Install Statping
|
||||
|
@ -12,7 +12,7 @@
|
|||
// brew install statping
|
||||
//
|
||||
// // Linux installation
|
||||
// bash <(curl -s https://assets.statping.com/install.sh)
|
||||
// curl -o- -L https://statping.com/install.sh | bash
|
||||
// statping version
|
||||
//
|
||||
// Docker
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
module github.com/hunterlong/statping
|
||||
|
||||
go 1.13.5
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.10.1
|
||||
github.com/GeertJohan/go.rice v1.0.0
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/agnivade/levenshtein v1.0.2 // indirect
|
||||
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d
|
||||
github.com/daaku/go.zipexe v1.0.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v1.13.1
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/go-mail/mail v2.3.1+incompatible
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/sessions v1.2.0
|
||||
github.com/gorilla/websocket v1.4.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.3 // indirect
|
||||
github.com/jinzhu/gorm v1.9.11
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/lib/pq v1.2.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
|
||||
github.com/vektah/gqlparser v1.1.2
|
||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/mail.v2 v2.3.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.2.7 // indirect
|
||||
)
|
|
@ -0,0 +1,265 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
github.com/99designs/gqlgen v0.10.1 h1:1BgB6XKGTHq7uH4G1/PYyKe2Kz7/vw3AlvMZlD3TEEY=
|
||||
github.com/99designs/gqlgen v0.10.1/go.mod h1:IviubpnyI4gbBcj8IcxSSc/Q/+af5riwCmJmwF0uaPE=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.2 h1:xKF7WlEzoa+ZVkzBxy0ukdzI2etYiWGlTPMNTBGncKI=
|
||||
github.com/agnivade/levenshtein v1.0.2/go.mod h1:JLvzGblJATanj48SD0YhHTEFGkWvw3ASLFWSiMIFXsE=
|
||||
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d h1:ZX0t+GA3MWiP7LWt5xWOphWRQd5JwL4VW5uLW83KM8g=
|
||||
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d/go.mod h1:EcJ034SpbWy4heOSDiBZJRn3b5wKJM1b4sFfYeVAkI4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/daaku/go.zipexe v1.0.1 h1:wV4zMsDOI2SZ2m7Tdz1Ps96Zrx+TzaK15VbUaGozw0M=
|
||||
github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM=
|
||||
github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
|
||||
github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f h1:onGP+qmYmjKs7pkmi9j0mwyr97/D5wki80e74aKIOxg=
|
||||
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f/go.mod h1:cq57a4l475CeMvE7RRpSui1MEqCmhirIt1E7kl8BC2Q=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e h1:nt2877sKfojlHCTOBXbpWjBkuWKritFaGIfgQwbQUls=
|
||||
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e/go.mod h1:B4+Kq1u5FlULTjFSM707Q6e/cOHFv0z/6QRoxubDIQ8=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE=
|
||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
|
|
@ -54,11 +54,11 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
|
||||
CacheStorage = NewStorage()
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
|
||||
utils.Log(2, fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
|
||||
log.Warnln(fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
|
||||
output := apiResponse{
|
||||
Status: "error",
|
||||
Error: err.Error(),
|
||||
|
@ -131,6 +131,7 @@ func sendUnauthorizedJson(w http.ResponseWriter, r *http.Request) {
|
|||
Status: "error",
|
||||
Error: errors.New("not authorized").Error(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
returnJson(output, w, r)
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@ package handlers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
_ "github.com/hunterlong/statping/notifiers"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -29,7 +32,9 @@ func init() {
|
|||
}
|
||||
|
||||
func TestResetDatabase(t *testing.T) {
|
||||
Clean()
|
||||
err := core.TmpRecords("handlers.db")
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, core.CoreApp)
|
||||
}
|
||||
|
||||
func TestFailedHTTPServer(t *testing.T) {
|
||||
|
@ -59,7 +64,7 @@ func TestSetupRoutes(t *testing.T) {
|
|||
Name: "Statping Setup Check",
|
||||
URL: "/setup",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: 303,
|
||||
},
|
||||
{
|
||||
Name: "Statping Run Setup",
|
||||
|
@ -68,7 +73,7 @@ func TestSetupRoutes(t *testing.T) {
|
|||
Body: form.Encode(),
|
||||
ExpectedStatus: 303,
|
||||
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
|
||||
ExpectedFiles: []string{utils.Directory + "/config.yml", utils.Directory + "/statup.db"},
|
||||
ExpectedFiles: []string{dir + "/config.yml", dir + "/tmp/" + types.SqliteFilename},
|
||||
}}
|
||||
|
||||
for _, v := range tests {
|
||||
|
@ -89,7 +94,7 @@ func TestMainApiRoutes(t *testing.T) {
|
|||
URL: "/api",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContains: []string{`"name":"Tester","description":"This is an awesome test"`},
|
||||
ExpectedContains: []string{`"name":"Statping Sample Data","description":"This data is only used to testing"`},
|
||||
},
|
||||
{
|
||||
Name: "Statping Renew API Keys",
|
||||
|
@ -113,11 +118,7 @@ func TestMainApiRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -234,11 +235,7 @@ func TestApiServiceRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -278,11 +275,7 @@ func TestGroupAPIRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -332,11 +325,7 @@ func TestApiUsersRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -369,11 +358,7 @@ func TestApiNotifiersRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -439,11 +424,7 @@ func TestMessagesApiRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -478,11 +459,7 @@ func TestApiCheckinRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -514,10 +491,7 @@ func RunHTTPTest(test HTTPTest, t *testing.T) (string, *testing.T, error) {
|
|||
}
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
if err != nil {
|
||||
assert.Nil(t, err)
|
||||
return "", t, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(rr.Result().Body)
|
||||
if err != nil {
|
||||
assert.Nil(t, err)
|
||||
|
@ -540,9 +514,3 @@ func RunHTTPTest(test HTTPTest, t *testing.T) (string, *testing.T, error) {
|
|||
}
|
||||
return stringBody, t, err
|
||||
}
|
||||
|
||||
func Clean() {
|
||||
utils.DeleteFile(dir + "/config.yml")
|
||||
utils.DeleteFile(dir + "/statup.db")
|
||||
utils.DeleteDirectory(dir + "/logs")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
@ -25,7 +26,7 @@ func (item Item) Expired() bool {
|
|||
if item.Expiration == 0 {
|
||||
return false
|
||||
}
|
||||
return time.Now().UnixNano() > item.Expiration
|
||||
return utils.Now().UnixNano() > item.Expiration
|
||||
}
|
||||
|
||||
//Storage mecanism for caching strings in memory
|
||||
|
@ -68,6 +69,6 @@ func (s Storage) Set(key string, content []byte, duration time.Duration) {
|
|||
defer s.mu.Unlock()
|
||||
s.items[key] = Item{
|
||||
Content: content,
|
||||
Expiration: time.Now().Add(duration).UnixNano(),
|
||||
Expiration: utils.Now().Add(duration).UnixNano(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/hunterlong/statping/utils"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -81,7 +80,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
|||
hit := &types.CheckinHit{
|
||||
Checkin: checkin.Id,
|
||||
From: ip,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
CreatedAt: utils.Now().UTC(),
|
||||
}
|
||||
checkinHit := core.ReturnCheckinHit(hit)
|
||||
if checkin.Last() == nil {
|
||||
|
@ -95,7 +94,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
checkin.Failing = false
|
||||
checkin.LastHit = utils.Timezoner(time.Now().UTC(), core.CoreApp.Timezone)
|
||||
checkin.LastHit = utils.Timezoner(utils.Now().UTC(), core.CoreApp.Timezone)
|
||||
sendJsonAction(checkinHit, "update", w, r)
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -50,8 +49,8 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|||
session.Values["user_id"] = user.Id
|
||||
session.Values["admin"] = user.Admin.Bool
|
||||
session.Save(r, w)
|
||||
utils.Log(1, fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
|
||||
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||
utils.Log.Infoln(fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
|
||||
http.Redirect(w, r, basePath+"dashboard", http.StatusSeeOther)
|
||||
} else {
|
||||
err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."}
|
||||
ExecuteResponse(w, r, "login.gohtml", err, nil)
|
||||
|
@ -64,12 +63,12 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) {
|
|||
session.Values["admin"] = false
|
||||
session.Values["user_id"] = 0
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func helpHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsUser(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
help := source.HelpMarkdown()
|
||||
|
@ -115,6 +114,6 @@ func exportHandler(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Length", strconv.Itoa(fileSize))
|
||||
w.Header().Set("Content-Control", "private, no-transform, no-store, must-revalidate")
|
||||
|
||||
http.ServeContent(w, r, "export.json", time.Now(), bytes.NewReader(export))
|
||||
http.ServeContent(w, r, "export.json", utils.Now(), bytes.NewReader(export))
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
@ -27,7 +27,7 @@ func TestGenericRoutes(t *testing.T) {
|
|||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContains: []string{
|
||||
`<title>Tester Status</title>`,
|
||||
`<title>Statping Sample Data Status</title>`,
|
||||
`<footer>`,
|
||||
},
|
||||
},
|
||||
|
@ -100,7 +100,7 @@ func TestGenericRoutes(t *testing.T) {
|
|||
URL: "/metrics",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContains: []string{"statping_total_services 5"},
|
||||
ExpectedContains: []string{"statping_total_services 15"},
|
||||
},
|
||||
{
|
||||
Name: "Last Log Line",
|
||||
|
@ -140,10 +140,7 @@ func TestGenericRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
basePath = "/"
|
||||
)
|
||||
|
||||
var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"js": func(html interface{}) template.JS {
|
||||
|
@ -63,6 +67,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
|||
"USE_CDN": func() bool {
|
||||
return core.CoreApp.UseCdn.Bool
|
||||
},
|
||||
"UPDATENOTIFY": func() bool {
|
||||
return core.CoreApp.UpdateNotify.Bool
|
||||
},
|
||||
"QrAuth": func() string {
|
||||
return fmt.Sprintf("statping://setup?domain=%v&api=%v", core.CoreApp.Domain, core.CoreApp.ApiSecret)
|
||||
},
|
||||
|
@ -85,7 +92,7 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
|||
return utils.UnderScoreString(html)
|
||||
},
|
||||
"URL": func() string {
|
||||
return r.URL.String()
|
||||
return basePath + r.URL.String()
|
||||
},
|
||||
"CHART_DATA": func() string {
|
||||
return ""
|
||||
|
@ -142,5 +149,8 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
|||
"NewGroup": func() *types.Group {
|
||||
return new(types.Group)
|
||||
},
|
||||
"BasePath": func() string {
|
||||
return basePath
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,19 +16,22 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -41,9 +44,8 @@ var (
|
|||
httpServer *http.Server
|
||||
usingSSL bool
|
||||
mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}`
|
||||
templates = []string{"base.gohtml", "head.gohtml", "nav.gohtml", "footer.gohtml", "scripts.gohtml", "form_service.gohtml", "form_notifier.gohtml", "form_group.gohtml", "form_user.gohtml", "form_checkin.gohtml", "form_message.gohtml"}
|
||||
templates = []string{"base.gohtml", "head.gohtml", "nav.gohtml", "footer.gohtml", "scripts.gohtml", "form_service.gohtml", "form_notifier.gohtml", "form_integration.gohtml", "form_group.gohtml", "form_user.gohtml", "form_checkin.gohtml", "form_message.gohtml"}
|
||||
javascripts = []string{"charts.js", "chart_index.js"}
|
||||
mainTemplate *template.Template
|
||||
)
|
||||
|
||||
// RunHTTPServer will start a HTTP server on a specific IP and port
|
||||
|
@ -54,11 +56,11 @@ func RunHTTPServer(ip string, port int) error {
|
|||
cert := utils.FileExists(utils.Directory + "/server.crt")
|
||||
|
||||
if key && cert {
|
||||
utils.Log(1, "server.cert and server.key was found in root directory! Starting in SSL mode.")
|
||||
utils.Log(1, fmt.Sprintf("Statping Secure HTTPS Server running on https://%v:%v", ip, 443))
|
||||
log.Infoln("server.cert and server.key was found in root directory! Starting in SSL mode.")
|
||||
log.Infoln(fmt.Sprintf("Statping Secure HTTPS Server running on https://%v:%v", ip, 443))
|
||||
usingSSL = true
|
||||
} else {
|
||||
utils.Log(1, "Statping HTTP Server running on http://"+host)
|
||||
log.Infoln("Statping HTTP Server running on http://" + host)
|
||||
}
|
||||
|
||||
router = Router()
|
||||
|
@ -102,17 +104,20 @@ func RunHTTPServer(ip string, port int) error {
|
|||
|
||||
// IsReadAuthenticated will allow Read Only authentication for some routes
|
||||
func IsReadAuthenticated(r *http.Request) bool {
|
||||
if core.SetupMode {
|
||||
return false
|
||||
}
|
||||
var token string
|
||||
query := r.URL.Query()
|
||||
key := query.Get("api")
|
||||
if key == core.CoreApp.ApiKey {
|
||||
if subtle.ConstantTimeCompare([]byte(key), []byte(core.CoreApp.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
if token == core.CoreApp.ApiKey {
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(core.CoreApp.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +133,9 @@ func IsFullAuthenticated(r *http.Request) bool {
|
|||
if core.CoreApp == nil {
|
||||
return true
|
||||
}
|
||||
if core.SetupMode {
|
||||
return false
|
||||
}
|
||||
if sessionStore == nil {
|
||||
return true
|
||||
}
|
||||
|
@ -136,7 +144,7 @@ func IsFullAuthenticated(r *http.Request) bool {
|
|||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
if token == core.CoreApp.ApiSecret {
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(core.CoreApp.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +153,9 @@ func IsFullAuthenticated(r *http.Request) bool {
|
|||
|
||||
// IsAdmin returns true if the user session is an administrator
|
||||
func IsAdmin(r *http.Request) bool {
|
||||
if core.SetupMode {
|
||||
return false
|
||||
}
|
||||
session, err := sessionStore.Get(r, cookieKey)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -157,6 +168,9 @@ func IsAdmin(r *http.Request) bool {
|
|||
|
||||
// IsUser returns true if the user is registered
|
||||
func IsUser(r *http.Request) bool {
|
||||
if core.SetupMode {
|
||||
return false
|
||||
}
|
||||
if os.Getenv("GO_ENV") == "test" {
|
||||
return true
|
||||
}
|
||||
|
@ -170,23 +184,22 @@ func IsUser(r *http.Request) bool {
|
|||
return session.Values["authenticated"].(bool)
|
||||
}
|
||||
|
||||
func loadTemplate(w http.ResponseWriter, r *http.Request) error {
|
||||
func loadTemplate(w http.ResponseWriter, r *http.Request) (*template.Template, error) {
|
||||
var err error
|
||||
mainTemplate = template.New("main")
|
||||
mainTemplate.Funcs(handlerFuncs(w, r))
|
||||
mainTemplate := template.New("main")
|
||||
mainTemplate, err = mainTemplate.Parse(mainTmpl)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
return err
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
// render all templates
|
||||
mainTemplate.Funcs(handlerFuncs(w, r))
|
||||
// render all templates
|
||||
for _, temp := range templates {
|
||||
tmp, _ := source.TmplBox.String(temp)
|
||||
mainTemplate, err = mainTemplate.Parse(tmp)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
return err
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// render all javascript files
|
||||
|
@ -194,35 +207,37 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
|
|||
tmp, _ := source.JsBox.String(temp)
|
||||
mainTemplate, err = mainTemplate.Parse(tmp)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
return err
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return err
|
||||
return mainTemplate, err
|
||||
}
|
||||
|
||||
// ExecuteResponse will render a HTTP response for the front end user
|
||||
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) {
|
||||
utils.Http(r)
|
||||
if url, ok := redirect.(string); ok {
|
||||
http.Redirect(w, r, url, http.StatusSeeOther)
|
||||
http.Redirect(w, r, path.Join(basePath, url), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if usingSSL {
|
||||
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
}
|
||||
loadTemplate(w, r)
|
||||
mainTemplate, err := loadTemplate(w, r)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
render, err := source.TmplBox.String(file)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
// render the page requested
|
||||
if _, err := mainTemplate.Parse(render); err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
// execute the template
|
||||
if err := mainTemplate.Execute(w, data); err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +245,7 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
|||
func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
|
||||
render, err := source.JsBox.String(file)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
if usingSSL {
|
||||
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
|
@ -245,10 +260,10 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
|
|||
},
|
||||
})
|
||||
if _, err := t.Parse(render); err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
)
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if core.Configs == nil {
|
||||
if core.CoreApp.Config == nil {
|
||||
http.Redirect(w, r, "/setup", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
|||
health := map[string]interface{}{
|
||||
"services": len(core.Services()),
|
||||
"online": true,
|
||||
"setup": core.Configs != nil,
|
||||
"setup": core.CoreApp.Config != nil,
|
||||
}
|
||||
returnJson(health, w, r)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statping/core/integrations"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func apiAllIntegrationsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
integrations := integrations.Integrations
|
||||
returnJson(integrations, w, r)
|
||||
}
|
||||
|
||||
func apiIntegrationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
intg := vars["name"]
|
||||
r.ParseForm()
|
||||
for k, v := range r.PostForm {
|
||||
log.Info(k, v)
|
||||
}
|
||||
|
||||
integration, err := integrations.Find(intg)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
list, err := integration.List()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
returnJson(list, w, r)
|
||||
}
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
func messagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsUser(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
messages, _ := core.SelectMessages()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -25,10 +25,7 @@ func TestMessageRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,60 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
authUser string
|
||||
authPass string
|
||||
)
|
||||
|
||||
// basicAuthHandler is a middleware to implement HTTP basic authentication using
|
||||
// AUTH_USERNAME and AUTH_PASSWORD environment variables
|
||||
func basicAuthHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok || subtle.ConstantTimeCompare([]byte(user),
|
||||
[]byte(authUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass),
|
||||
[]byte(authPass)) != 1 {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="statping"`)
|
||||
w.WriteHeader(401)
|
||||
w.Write([]byte("You are unauthorized to access the application.\n"))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// sendLog is a http middleware that will log the duration of request and other useful fields
|
||||
func sendLog(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t1 := utils.Now()
|
||||
t2 := utils.Now().Sub(t1)
|
||||
if r.RequestURI == "/logs/line" {
|
||||
return
|
||||
}
|
||||
log.WithFields(utils.ToFields(w, r)).
|
||||
WithField("url", r.RequestURI).
|
||||
WithField("method", r.Method).
|
||||
WithField("load_micro_seconds", t2.Microseconds()).
|
||||
Infoln(fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// authenticated is a middleware function to check if user is an Admin before running original request
|
||||
func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
if redirect {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
} else {
|
||||
sendUnauthorizedJson(w, r)
|
||||
}
|
||||
|
@ -27,7 +69,7 @@ func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect boo
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
if redirect {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
} else {
|
||||
sendUnauthorizedJson(w, r)
|
||||
}
|
||||
|
@ -43,7 +85,7 @@ func cached(duration, contentType string, handler func(w http.ResponseWriter, r
|
|||
content := CacheStorage.Get(r.RequestURI)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if core.Configs == nil {
|
||||
if core.CoreApp.Config == nil {
|
||||
handler(w, r)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -85,8 +85,8 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
fakeNotifer, notif, err := notifier.SelectNotifier(method)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("issue saving notifier %v: %v", method, err))
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
log.Errorln(fmt.Sprintf("issue saving notifier %v: %v", method, err))
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -25,11 +25,12 @@ import (
|
|||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
"time"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
router *mux.Router
|
||||
log = utils.Log.WithField("type", "handlers")
|
||||
)
|
||||
|
||||
// Router returns all of the routes used in Statping.
|
||||
|
@ -37,23 +38,38 @@ var (
|
|||
func Router() *mux.Router {
|
||||
dir := utils.Directory
|
||||
CacheStorage = NewStorage()
|
||||
r := mux.NewRouter()
|
||||
r.Handle("/", http.HandlerFunc(indexHandler))
|
||||
r := mux.NewRouter().StrictSlash(true)
|
||||
|
||||
if os.Getenv("AUTH_USERNAME") != "" && os.Getenv("AUTH_PASSWORD") != "" {
|
||||
authUser = os.Getenv("AUTH_USERNAME")
|
||||
authPass = os.Getenv("AUTH_PASSWORD")
|
||||
r.Use(basicAuthHandler)
|
||||
}
|
||||
|
||||
if os.Getenv("BASE_PATH") != "" {
|
||||
basePath = "/" + os.Getenv("BASE_PATH") + "/"
|
||||
r = r.PathPrefix("/" + os.Getenv("BASE_PATH")).Subrouter()
|
||||
r.Handle("", http.HandlerFunc(indexHandler))
|
||||
} else {
|
||||
r.Handle("/", http.HandlerFunc(indexHandler))
|
||||
}
|
||||
|
||||
r.Use(sendLog)
|
||||
if source.UsingAssets(dir) {
|
||||
indexHandler := http.FileServer(http.Dir(dir + "/assets/"))
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir(dir+"/assets/css"))))
|
||||
r.PathPrefix("/font/").Handler(http.StripPrefix("/font/", http.FileServer(http.Dir(dir+"/assets/font"))))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir(dir+"/assets/js"))))
|
||||
r.PathPrefix("/robots.txt").Handler(indexHandler)
|
||||
r.PathPrefix("/favicon.ico").Handler(indexHandler)
|
||||
r.PathPrefix("/banner.png").Handler(indexHandler)
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath+"css/", http.FileServer(http.Dir(dir+"/assets/css"))))
|
||||
r.PathPrefix("/font/").Handler(http.StripPrefix(basePath+"font/", http.FileServer(http.Dir(dir+"/assets/font"))))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath+"js/", http.FileServer(http.Dir(dir+"/assets/js"))))
|
||||
r.PathPrefix("/robots.txt").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
r.PathPrefix("/favicon.ico").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
r.PathPrefix("/banner.png").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
} else {
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(source.CssBox.HTTPBox())))
|
||||
r.PathPrefix("/font/").Handler(http.StripPrefix("/font/", http.FileServer(source.FontBox.HTTPBox())))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(source.JsBox.HTTPBox())))
|
||||
r.PathPrefix("/robots.txt").Handler(http.FileServer(source.TmplBox.HTTPBox()))
|
||||
r.PathPrefix("/favicon.ico").Handler(http.FileServer(source.TmplBox.HTTPBox()))
|
||||
r.PathPrefix("/banner.png").Handler(http.FileServer(source.TmplBox.HTTPBox()))
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath+"css/", http.FileServer(source.CssBox.HTTPBox())))
|
||||
r.PathPrefix("/font/").Handler(http.StripPrefix(basePath+"font/", http.FileServer(source.FontBox.HTTPBox())))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath+"js/", http.FileServer(source.JsBox.HTTPBox())))
|
||||
r.PathPrefix("/robots.txt").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/favicon.ico").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/banner.png").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
}
|
||||
r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler))
|
||||
r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET")
|
||||
|
@ -86,15 +102,26 @@ func Router() *mux.Router {
|
|||
r.Handle("/settings/delete_assets", authenticated(deleteAssetsHandler, true)).Methods("GET")
|
||||
r.Handle("/settings/export", authenticated(exportHandler, true)).Methods("GET")
|
||||
r.Handle("/settings/bulk_import", authenticated(bulkImportHandler, true)).Methods("POST")
|
||||
r.Handle("/settings/integrator/{name}", authenticated(integratorHandler, true)).Methods("POST")
|
||||
|
||||
// SERVICE Routes
|
||||
r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET")
|
||||
r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}", readOnly(servicesViewHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET")
|
||||
|
||||
r.Handle("/group/{id}", http.HandlerFunc(groupViewHandler)).Methods("GET")
|
||||
|
||||
// API Routes
|
||||
r.Handle("/api", authenticated(apiIndexHandler, false))
|
||||
r.Handle("/api/renew", authenticated(apiRenewHandler, false))
|
||||
r.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
|
||||
|
||||
r.Handle("/api/integrations", authenticated(apiAllIntegrationsHandler, false)).Methods("GET")
|
||||
r.Handle("/api/integrations/{name}", authenticated(apiIntegrationHandler, false)).Methods("GET")
|
||||
r.Handle("/api/integrations/{name}", authenticated(apiIntegrationHandler, false)).Methods("POST")
|
||||
|
||||
// API GROUPS Routes
|
||||
r.Handle("/api/groups", readOnly(apiAllGroupHandler, false)).Methods("GET")
|
||||
r.Handle("/api/groups", authenticated(apiCreateGroupHandler, false)).Methods("POST")
|
||||
|
@ -103,20 +130,15 @@ func Router() *mux.Router {
|
|||
r.Handle("/api/groups/{id}", authenticated(apiGroupDeleteHandler, false)).Methods("DELETE")
|
||||
r.Handle("/api/reorder/groups", authenticated(apiGroupReorderHandler, false)).Methods("POST")
|
||||
|
||||
// API Routes
|
||||
r.Handle("/api", authenticated(apiIndexHandler, false))
|
||||
r.Handle("/api/renew", authenticated(apiRenewHandler, false))
|
||||
r.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
|
||||
|
||||
// API SERVICE Routes
|
||||
r.Handle("/api/services", readOnly(apiAllServicesHandler, false)).Methods("GET")
|
||||
r.Handle("/api/services", authenticated(apiCreateServiceHandler, false)).Methods("POST")
|
||||
r.Handle("/api/services/{id}", readOnly(apiServiceHandler, false)).Methods("GET")
|
||||
r.Handle("/api/reorder/services", authenticated(reorderServiceHandler, false)).Methods("POST")
|
||||
r.Handle("/api/services/{id}/running", authenticated(apiServiceRunningHandler, false)).Methods("POST")
|
||||
r.Handle("/api/services/{id}/data", cached("30s", "application/json", http.HandlerFunc(apiServiceDataHandler))).Methods("GET")
|
||||
r.Handle("/api/services/{id}/ping", cached("30s", "application/json", http.HandlerFunc(apiServicePingDataHandler))).Methods("GET")
|
||||
r.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", http.HandlerFunc(apiServiceHeatmapHandler))).Methods("GET")
|
||||
r.Handle("/api/services/{id}/data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
|
||||
r.Handle("/api/services/{id}/ping", cached("30s", "application/json", apiServicePingDataHandler)).Methods("GET")
|
||||
r.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", apiServiceHeatmapHandler)).Methods("GET")
|
||||
r.Handle("/api/services/{id}", authenticated(apiServiceUpdateHandler, false)).Methods("POST")
|
||||
r.Handle("/api/services/{id}", authenticated(apiServiceDeleteHandler, false)).Methods("DELETE")
|
||||
r.Handle("/api/services/{id}/failures", authenticated(apiServiceFailuresHandler, false)).Methods("GET")
|
||||
|
@ -174,7 +196,7 @@ func resetRouter() {
|
|||
|
||||
func resetCookies() {
|
||||
if core.CoreApp != nil {
|
||||
cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, time.Now().Nanosecond())
|
||||
cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, utils.Now().Nanosecond())
|
||||
sessionStore = sessions.NewCookieStore([]byte(cookie))
|
||||
} else {
|
||||
sessionStore = sessions.NewCookieStore([]byte("secretinfo"))
|
||||
|
|
|
@ -33,8 +33,8 @@ func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Type", "text/javascript")
|
||||
w.Header().Set("Cache-Control", "max-age=60")
|
||||
|
||||
end := time.Now().UTC()
|
||||
start := time.Now().Add((-24 * 7) * time.Hour).UTC()
|
||||
end := utils.Now().UTC()
|
||||
start := utils.Now().Add((-24 * 7) * time.Hour).UTC()
|
||||
var srvs []*core.Service
|
||||
for _, s := range services {
|
||||
srvs = append(srvs, s.(*core.Service))
|
||||
|
@ -94,7 +94,7 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
endField := utils.ToInt(fields.Get("end"))
|
||||
group := r.Form.Get("group")
|
||||
|
||||
end := time.Now().UTC()
|
||||
end := utils.Now().UTC()
|
||||
start := end.Add((-24 * 7) * time.Hour).UTC()
|
||||
|
||||
if startField != 0 {
|
||||
|
@ -243,7 +243,7 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
|
|||
var monthOutput []*dataXyMonth
|
||||
|
||||
start := service.CreatedAt
|
||||
//now := time.Now()
|
||||
//now := utils.Now()
|
||||
|
||||
sY, sM, _ := start.Date()
|
||||
|
||||
|
@ -252,10 +252,10 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
|
|||
month := int(sM)
|
||||
maxMonth := 12
|
||||
|
||||
for year := int(sY); year <= time.Now().Year(); year++ {
|
||||
for year := int(sY); year <= utils.Now().Year(); year++ {
|
||||
|
||||
if year == time.Now().Year() {
|
||||
maxMonth = int(time.Now().Month())
|
||||
if year == utils.Now().Year() {
|
||||
maxMonth = int(utils.Now().Month())
|
||||
}
|
||||
|
||||
for m := month; m <= maxMonth; m++ {
|
||||
|
@ -335,3 +335,7 @@ func apiServiceHitsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
returnJson(hits, w, r)
|
||||
}
|
||||
|
||||
func createServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ExecuteResponse(w, r, "service_create.gohtml", core.CoreApp, nil)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -31,10 +31,7 @@ func TestServiceRoutes(t *testing.T) {
|
|||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ package handlers
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/core/integrations"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
|
@ -62,13 +64,16 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
timeFloat, _ := strconv.ParseFloat(timezone, 10)
|
||||
app.Timezone = float32(timeFloat)
|
||||
|
||||
app.UpdateNotify = types.NewNullBool(form.Get("update_notify") == "true")
|
||||
|
||||
app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on")
|
||||
core.CoreApp, err = core.UpdateCore(app)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error()))
|
||||
log.Errorln(fmt.Sprintf("issue updating Core: %v", err.Error()))
|
||||
}
|
||||
|
||||
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
|
||||
}
|
||||
|
||||
func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -81,37 +86,37 @@ func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
|||
source.SaveAsset([]byte(mobile), utils.Directory, "scss/mobile.scss")
|
||||
source.CompileSASS(utils.Directory)
|
||||
resetRouter()
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
|
||||
}
|
||||
|
||||
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
dir := utils.Directory
|
||||
if err := source.CreateAllAssets(dir); err != nil {
|
||||
utils.Log(3, err)
|
||||
log.Errorln(err)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if err := source.CompileSASS(dir); err != nil {
|
||||
source.CopyToPublic(source.CssBox, dir+"/assets/css", "base.css")
|
||||
utils.Log(3, "Default 'base.css' was inserted because SASS did not work.")
|
||||
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
|
||||
}
|
||||
resetRouter()
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
|
||||
}
|
||||
|
||||
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := source.DeleteAllAssets(utils.Directory); err != nil {
|
||||
utils.Log(3, fmt.Errorf("error deleting all assets %v", err))
|
||||
log.Errorln(fmt.Errorf("error deleting all assets %v", err))
|
||||
}
|
||||
resetRouter()
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
|
||||
}
|
||||
|
||||
func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var fileData bytes.Buffer
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Errorf("error bulk import services: %v", err))
|
||||
log.Errorln(fmt.Errorf("error bulk import services: %v", err))
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
@ -125,26 +130,74 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
newService, err := commaToService(col)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Errorf("issue with row %v: %v", i, err))
|
||||
log.Errorln(fmt.Errorf("issue with row %v: %v", i, err))
|
||||
continue
|
||||
}
|
||||
|
||||
service := core.ReturnService(newService)
|
||||
_, err = service.Create(true)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Errorf("cannot create service %v: %v", col[0], err))
|
||||
log.Errorln(fmt.Errorf("cannot create service %v: %v", col[0], err))
|
||||
continue
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Created new service %v", service.Name))
|
||||
log.Infoln(fmt.Sprintf("Created new service %v", service.Name))
|
||||
}
|
||||
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
}
|
||||
|
||||
type integratorOut struct {
|
||||
Integrator *types.Integration `json:"integrator"`
|
||||
Services []*types.Service `json:"services"`
|
||||
Error error
|
||||
}
|
||||
|
||||
func integratorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
integratorName := vars["name"]
|
||||
r.ParseForm()
|
||||
|
||||
integrator, err := integrations.Find(integratorName)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
|
||||
Error: err,
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info(r.PostForm)
|
||||
|
||||
for _, v := range integrator.Get().Fields {
|
||||
log.Info(v.Name, v.Value)
|
||||
}
|
||||
|
||||
integrations.SetFields(integrator, r.PostForm)
|
||||
|
||||
for _, v := range integrator.Get().Fields {
|
||||
log.Info(v.Name, v.Value)
|
||||
}
|
||||
|
||||
services, err := integrator.List()
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
|
||||
Integrator: integrator.Get(),
|
||||
Error: err,
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
|
||||
Integrator: integrator.Get(),
|
||||
Services: services,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// commaToService will convert a CSV comma delimited string slice to a Service type
|
||||
// this function is used for the bulk import services feature
|
||||
func commaToService(s []string) (*types.Service, error) {
|
||||
if len(s) != 16 {
|
||||
if len(s) != 17 {
|
||||
err := fmt.Errorf("does not have the expected amount of %v columns for a service", 16)
|
||||
return nil, err
|
||||
}
|
||||
|
@ -169,6 +222,11 @@ func commaToService(s []string) (*types.Service, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
verifySsl, err := strconv.ParseBool(s[16])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newService := &types.Service{
|
||||
Name: s[0],
|
||||
Domain: s[1],
|
||||
|
@ -185,6 +243,7 @@ func commaToService(s []string) (*types.Service, error) {
|
|||
GroupId: int(utils.ToInt(s[13])),
|
||||
Headers: types.NewNullString(s[14]),
|
||||
Permalink: types.NewNullString(s[15]),
|
||||
VerifySSL: types.NewNullBool(verifySsl),
|
||||
}
|
||||
|
||||
return newService, nil
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
func setupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if core.CoreApp.Services != nil {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var data interface{}
|
||||
|
@ -39,8 +39,8 @@ func setupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
if core.Services() != nil {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
if !core.SetupMode {
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
|
@ -57,10 +57,9 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
domain := r.PostForm.Get("domain")
|
||||
email := r.PostForm.Get("email")
|
||||
sample := r.PostForm.Get("sample_data") == "on"
|
||||
utils.Log(2, sample)
|
||||
dir := utils.Directory
|
||||
|
||||
config := &core.DbConfig{
|
||||
config := &types.DbConfig{
|
||||
DbConn: dbConn,
|
||||
DbHost: dbHost,
|
||||
DbUser: dbUser,
|
||||
|
@ -77,34 +76,36 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
Location: utils.Directory,
|
||||
}
|
||||
|
||||
if core.Configs, err = config.Save(); err != nil {
|
||||
utils.Log(4, err)
|
||||
log.WithFields(utils.ToFields(core.CoreApp, config)).Debugln("new configs posted")
|
||||
|
||||
if _, err := core.CoreApp.SaveConfig(config); err != nil {
|
||||
log.Errorln(err)
|
||||
config.Error = err
|
||||
setupResponseError(w, r, config)
|
||||
return
|
||||
}
|
||||
|
||||
if core.Configs, err = core.LoadConfigFile(dir); err != nil {
|
||||
utils.Log(3, err)
|
||||
if _, err = core.LoadConfigFile(dir); err != nil {
|
||||
log.Errorln(err)
|
||||
config.Error = err
|
||||
setupResponseError(w, r, config)
|
||||
return
|
||||
}
|
||||
|
||||
if err = core.Configs.Connect(false, dir); err != nil {
|
||||
utils.Log(4, err)
|
||||
if err = core.CoreApp.Connect(false, dir); err != nil {
|
||||
log.Errorln(err)
|
||||
core.DeleteConfig()
|
||||
config.Error = err
|
||||
setupResponseError(w, r, config)
|
||||
return
|
||||
}
|
||||
|
||||
config.DropDatabase()
|
||||
config.CreateDatabase()
|
||||
core.CoreApp.DropDatabase()
|
||||
core.CoreApp.CreateDatabase()
|
||||
|
||||
core.CoreApp, err = config.InsertCore()
|
||||
core.CoreApp, err = core.CoreApp.InsertCore(config)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
log.Errorln(err)
|
||||
config.Error = err
|
||||
setupResponseError(w, r, config)
|
||||
return
|
||||
|
@ -125,7 +126,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
CacheStorage.Delete("/")
|
||||
resetCookies()
|
||||
time.Sleep(2 * time.Second)
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
http.Redirect(w, r, basePath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func setupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -19,16 +19,13 @@ func TestUserRoutes(t *testing.T) {
|
|||
URL: "/user/2",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContains: []string{`<title>Statping | adminuser2</title>`},
|
||||
ExpectedContains: []string{`<title>Statping | testadmin2</title>`},
|
||||
}}
|
||||
|
||||
for _, v := range tests {
|
||||
t.Run(v.Name, func(t *testing.T) {
|
||||
_, t, err := RunHTTPTest(v, t)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
require.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
build:
|
||||
docker:
|
||||
web: Dockerfile
|
||||
run:
|
||||
web: statping -port $PORT
|
|
@ -0,0 +1,175 @@
|
|||
#!/bin/bash
|
||||
# Statping installation script for Linux, Mac, and maybe Windows.
|
||||
#
|
||||
# This installation script is a modification of Yarn's installation
|
||||
set -e
|
||||
|
||||
reset="\033[0m"
|
||||
red="\033[31m"
|
||||
green="\033[32m"
|
||||
yellow="\033[33m"
|
||||
cyan="\033[36m"
|
||||
white="\033[37m"
|
||||
gpg_key=64B9C6AAE2D55278
|
||||
gpgurl=https://statping.com/statping.gpg
|
||||
repo=https://github.com/hunterlong/statping
|
||||
|
||||
statping_get_tarball() {
|
||||
fext='tar.gz'
|
||||
if [ ${OS} == 'windows' ]; then
|
||||
fext='zip'
|
||||
ARCH='x64'
|
||||
fi
|
||||
url="$repo/releases/latest/download/statping-$1-$2.$fext"
|
||||
printf "$cyan> Downloading latest version for $OS $ARCH...\n$url $reset\n"
|
||||
# Get both the tarball and its GPG signature
|
||||
tarball_tmp=`mktemp -t statping.tar.gz.XXXXXXXXXX`
|
||||
if curl --fail -L -o "$tarball_tmp" "$url"; then
|
||||
# All this dance is because `tar --strip=1` does not work everywhere
|
||||
temp=$(mktemp -d statping.XXXXXXXXXX)
|
||||
if [ ${OS} == 'windows' ]; then
|
||||
unzip $tarball_tmp -d "$temp"
|
||||
else
|
||||
tar xzf $tarball_tmp -C "$temp"
|
||||
fi
|
||||
statping_verify_integrity "$temp"/statping
|
||||
printf "$green> Installing to $DEST/statping\n"
|
||||
mv "$temp"/statping "$DEST"
|
||||
newversion=`$DEST/statping version`
|
||||
rm -rf "$temp"
|
||||
rm $tarball_tmp*
|
||||
printf "$cyan> Statping is now installed! $reset\n"
|
||||
printf "$white> Version: $newversion $reset\n"
|
||||
printf "$white> Repo: $repo $reset\n"
|
||||
printf "$white> Wiki: $repo/wiki $reset\n"
|
||||
printf "$white> Issues: $repo/issues $reset\n"
|
||||
printf "$cyan> Try to run \"statping help\" $reset\n"
|
||||
else
|
||||
printf "$red> Failed to download $url.$reset\n"
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
# Verifies the GPG signature of the tarball
|
||||
statping_verify_integrity() {
|
||||
# Check if GPG is installed
|
||||
if [[ -z "$(command -v gpg)" ]]; then
|
||||
printf "$yellow> WARNING: GPG is not installed, integrity can not be verified!$reset\n"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$statping_GPG" == "no" ]; then
|
||||
printf "$cyan> WARNING: Skipping GPG integrity check!$reset\n"
|
||||
return
|
||||
fi
|
||||
|
||||
printf "$cyan> Verifying integrity with gpg key from $gpgurl...$reset\n"
|
||||
# Grab the public key if it doesn't already exist
|
||||
gpg --list-keys $gpg_key >/dev/null 2>&1 || (curl -sS -L $gpgurl | gpg --import)
|
||||
|
||||
if [ ! -f "$1.asc" ]; then
|
||||
printf "$red> Could not download GPG signature for this Statping release. This means the release can not be verified!$reset\n"
|
||||
statping_verify_or_quit "> Do you really want to continue?"
|
||||
return
|
||||
fi
|
||||
|
||||
# Actually perform the verification
|
||||
if gpg --verify "$1.asc" $1 &> /dev/null; then
|
||||
printf "$green> GPG signature looks good$reset\n"
|
||||
else
|
||||
printf "$red> GPG signature for this Statping release is invalid! This is BAD and may mean the release has been tampered with. It is strongly recommended that you report this to the Statping developers.$reset\n"
|
||||
statping_verify_or_quit "> Do you really want to continue?"
|
||||
fi
|
||||
}
|
||||
|
||||
statping_reset() {
|
||||
unset -f statping_install statping_reset statping_get_tarball statping_verify_integrity statping_verify_or_quit statping_brew_install getOS getArch
|
||||
}
|
||||
|
||||
statping_brew_install() {
|
||||
if [[ -z "$(command -v brew --version)" ]]; then
|
||||
printf "${white}Using Brew to install!$reset\n"
|
||||
printf "${yellow}---> brew tap hunterlong/statping$reset\n"
|
||||
brew tap hunterlong/statping
|
||||
printf "${yellow}---> brew install statping$reset\n"
|
||||
brew install statping
|
||||
printf "${green}Brew installation is complete!$reset\n"
|
||||
printf "${yellow}You can use 'brew upgrade' to upgrade Statping next time.$reset\n"
|
||||
else
|
||||
statping_get_tarball $OS $ARCH
|
||||
fi
|
||||
}
|
||||
|
||||
statping_install() {
|
||||
printf "${white}Installing Statping!$reset\n"
|
||||
getOS
|
||||
getArch
|
||||
if [ "$OS" == "osx" ]; then
|
||||
statping_brew_install
|
||||
else
|
||||
statping_get_tarball $OS $ARCH
|
||||
fi
|
||||
statping_reset
|
||||
}
|
||||
|
||||
statping_verify_or_quit() {
|
||||
read -p "$1 [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]
|
||||
then
|
||||
printf "$red> Aborting$reset\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# get the users operating system
|
||||
getOS() {
|
||||
OS="`uname`"
|
||||
case $OS in
|
||||
'Linux')
|
||||
OS='linux'
|
||||
DEST=/usr/local/bin
|
||||
alias ls='ls --color=auto'
|
||||
;;
|
||||
'FreeBSD')
|
||||
OS='freebsd'
|
||||
DEST=/usr/local/bin
|
||||
alias ls='ls -G'
|
||||
;;
|
||||
'WindowsNT')
|
||||
OS='windows'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'MINGW*')
|
||||
OS='windows'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'CYGWIN*')
|
||||
OS='windows'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'Darwin')
|
||||
OS='osx'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'SunOS')
|
||||
OS='solaris'
|
||||
DEST=/usr/local/bin
|
||||
;;
|
||||
'AIX') ;;
|
||||
*) ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# get 64x or 32 machine arch
|
||||
getArch() {
|
||||
MACHINE_TYPE=`uname -m`
|
||||
if [ ${MACHINE_TYPE} == 'x86_64' ]; then
|
||||
ARCH="x64"
|
||||
else
|
||||
ARCH="x32"
|
||||
fi
|
||||
}
|
||||
|
||||
cd ~
|
||||
statping_install $1 $2
|
|
@ -27,10 +27,10 @@ type commandLine struct {
|
|||
*notifier.Notification
|
||||
}
|
||||
|
||||
var command = &commandLine{¬ifier.Notification{
|
||||
Method: "command",
|
||||
var Command = &commandLine{¬ifier.Notification{
|
||||
Method: "Command",
|
||||
Title: "Shell Command",
|
||||
Description: "Shell Command allows you to run a customized shell/bash command on the local machine it's running on.",
|
||||
Description: "Shell Command allows you to run a customized shell/bash Command on the local machine it's running on.",
|
||||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
Delay: time.Duration(1 * time.Second),
|
||||
|
@ -47,24 +47,16 @@ var command = &commandLine{¬ifier.Notification{
|
|||
Title: "Command to Run on OnSuccess",
|
||||
Placeholder: "curl google.com",
|
||||
DbField: "var1",
|
||||
SmallText: "This command will run every time a service is receiving a Successful event.",
|
||||
SmallText: "This Command will run every time a service is receiving a Successful event.",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "Command to Run on OnFailure",
|
||||
Placeholder: "curl offline.com",
|
||||
DbField: "var2",
|
||||
SmallText: "This command will run every time a service is receiving a Failing event.",
|
||||
SmallText: "This Command will run every time a service is receiving a Failing event.",
|
||||
}}},
|
||||
}
|
||||
|
||||
// init the command notifier
|
||||
func init() {
|
||||
err := notifier.AddNotifier(command)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func runCommand(app, cmd string) (string, string, error) {
|
||||
outStr, errStr, err := utils.Command(cmd)
|
||||
return outStr, errStr, err
|
||||
|
@ -77,16 +69,14 @@ func (u *commandLine) Select() *notifier.Notification {
|
|||
// OnFailure for commandLine will trigger failing service
|
||||
func (u *commandLine) OnFailure(s *types.Service, f *types.Failure) {
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var2)
|
||||
u.Online = false
|
||||
}
|
||||
|
||||
// OnSuccess for commandLine will trigger successful service
|
||||
func (u *commandLine) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
if !s.Online {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var1)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave for commandLine triggers when this notifier has been saved
|
||||
|
@ -99,12 +89,12 @@ func (u *commandLine) OnSave() error {
|
|||
// OnTest for commandLine triggers when this notifier has been saved
|
||||
func (u *commandLine) OnTest() error {
|
||||
in, out, err := runCommand(u.Host, u.Var1)
|
||||
utils.Log(1, in)
|
||||
utils.Log(1, out)
|
||||
utils.Log.Infoln(in)
|
||||
utils.Log.Infoln(out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Send for commandLine will send message to expo command push notifications endpoint
|
||||
// Send for commandLine will send message to expo Command push notifications endpoint
|
||||
func (u *commandLine) Send(msg interface{}) error {
|
||||
cmd := msg.(string)
|
||||
_, _, err := runCommand(u.Host, cmd)
|
||||
|
|
|
@ -28,96 +28,75 @@ const (
|
|||
|
||||
func TestCommandNotifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
command.Host = "sh"
|
||||
command.Var1 = commandTest
|
||||
command.Var2 = commandTest
|
||||
Command.Host = "sh"
|
||||
Command.Var1 = commandTest
|
||||
Command.Var2 = commandTest
|
||||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load command", func(t *testing.T) {
|
||||
command.Host = "sh"
|
||||
command.Var1 = commandTest
|
||||
command.Var2 = commandTest
|
||||
command.Delay = time.Duration(100 * time.Millisecond)
|
||||
command.Limits = 99
|
||||
err := notifier.AddNotifier(command)
|
||||
t.Run("Load Command", func(t *testing.T) {
|
||||
Command.Host = "sh"
|
||||
Command.Var1 = commandTest
|
||||
Command.Var2 = commandTest
|
||||
Command.Delay = time.Duration(100 * time.Millisecond)
|
||||
Command.Limits = 99
|
||||
err := notifier.AddNotifiers(Command)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", command.Author)
|
||||
assert.Equal(t, "sh", command.Host)
|
||||
assert.Equal(t, commandTest, command.Var1)
|
||||
assert.Equal(t, commandTest, command.Var2)
|
||||
assert.Equal(t, "Hunter Long", Command.Author)
|
||||
assert.Equal(t, "sh", Command.Host)
|
||||
assert.Equal(t, commandTest, Command.Var1)
|
||||
assert.Equal(t, commandTest, Command.Var2)
|
||||
})
|
||||
|
||||
t.Run("Load command Notifier", func(t *testing.T) {
|
||||
notifier.Load()
|
||||
t.Run("Command Notifier Tester", func(t *testing.T) {
|
||||
assert.True(t, Command.CanTest())
|
||||
})
|
||||
|
||||
t.Run("command Notifier Tester", func(t *testing.T) {
|
||||
assert.True(t, command.CanTest())
|
||||
})
|
||||
|
||||
t.Run("command Within Limits", func(t *testing.T) {
|
||||
ok, err := command.WithinLimits()
|
||||
t.Run("Command Within Limits", func(t *testing.T) {
|
||||
ok, err := Command.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("command OnFailure", func(t *testing.T) {
|
||||
command.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(command.Queue))
|
||||
t.Run("Command OnFailure", func(t *testing.T) {
|
||||
Command.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(Command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command OnFailure multiple times", func(t *testing.T) {
|
||||
for i := 0; i <= 50; i++ {
|
||||
command.OnFailure(TestService, TestFailure)
|
||||
}
|
||||
assert.Equal(t, 52, len(command.Queue))
|
||||
t.Run("Command OnSuccess", func(t *testing.T) {
|
||||
Command.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command Check Offline", func(t *testing.T) {
|
||||
assert.False(t, command.Online)
|
||||
t.Run("Command OnSuccess Again", func(t *testing.T) {
|
||||
Command.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Command.Queue))
|
||||
go notifier.Queue(Command)
|
||||
time.Sleep(20 * time.Second)
|
||||
assert.Equal(t, 0, len(Command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command OnSuccess", func(t *testing.T) {
|
||||
command.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command Queue after being online", func(t *testing.T) {
|
||||
assert.True(t, command.Online)
|
||||
assert.Equal(t, 1, len(command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command OnSuccess Again", func(t *testing.T) {
|
||||
assert.True(t, command.Online)
|
||||
command.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(command.Queue))
|
||||
go notifier.Queue(command)
|
||||
time.Sleep(5 * time.Second)
|
||||
assert.Equal(t, 0, len(command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command Within Limits again", func(t *testing.T) {
|
||||
ok, err := command.WithinLimits()
|
||||
t.Run("Command Within Limits again", func(t *testing.T) {
|
||||
ok, err := Command.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("command Send", func(t *testing.T) {
|
||||
command.Send(commandTest)
|
||||
assert.Equal(t, 0, len(command.Queue))
|
||||
t.Run("Command Send", func(t *testing.T) {
|
||||
Command.Send(commandTest)
|
||||
assert.Equal(t, 0, len(Command.Queue))
|
||||
})
|
||||
|
||||
t.Run("command Test", func(t *testing.T) {
|
||||
command.OnTest()
|
||||
t.Run("Command Test", func(t *testing.T) {
|
||||
Command.OnTest()
|
||||
})
|
||||
|
||||
t.Run("command Queue", func(t *testing.T) {
|
||||
go notifier.Queue(command)
|
||||
t.Run("Command Queue", func(t *testing.T) {
|
||||
go notifier.Queue(Command)
|
||||
time.Sleep(5 * time.Second)
|
||||
assert.Equal(t, "sh", command.Host)
|
||||
assert.Equal(t, commandTest, command.Var1)
|
||||
assert.Equal(t, commandTest, command.Var2)
|
||||
assert.Equal(t, 0, len(command.Queue))
|
||||
assert.Equal(t, "sh", Command.Host)
|
||||
assert.Equal(t, commandTest, Command.Var1)
|
||||
assert.Equal(t, commandTest, Command.Var2)
|
||||
assert.Equal(t, 0, len(Command.Queue))
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -31,10 +31,10 @@ type discord struct {
|
|||
*notifier.Notification
|
||||
}
|
||||
|
||||
var discorder = &discord{¬ifier.Notification{
|
||||
var Discorder = &discord{¬ifier.Notification{
|
||||
Method: "discord",
|
||||
Title: "discord",
|
||||
Description: "Send notifications to your discord channel using discord webhooks. Insert your discord channel webhook URL to receive notifications. Based on the <a href=\"https://discordapp.com/developers/docs/resources/webhook\">discord webhooker API</a>.",
|
||||
Description: "Send notifications to your discord channel using discord webhooks. Insert your discord channel Webhook URL to receive notifications. Based on the <a href=\"https://discordapp.com/developers/docs/resources/Webhook\">discord webhooker API</a>.",
|
||||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
Delay: time.Duration(5 * time.Second),
|
||||
|
@ -43,23 +43,15 @@ var discorder = &discord{¬ifier.Notification{
|
|||
Form: []notifier.NotificationForm{{
|
||||
Type: "text",
|
||||
Title: "discord webhooker URL",
|
||||
Placeholder: "Insert your webhook URL here",
|
||||
Placeholder: "Insert your Webhook URL here",
|
||||
DbField: "host",
|
||||
}}},
|
||||
}
|
||||
|
||||
// init the discord notifier
|
||||
func init() {
|
||||
err := notifier.AddNotifier(discorder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send will send a HTTP Post to the discord API. It accepts type: []byte
|
||||
func (u *discord) Send(msg interface{}) error {
|
||||
message := msg.(string)
|
||||
_, _, err := utils.HttpRequest(discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second))
|
||||
_, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -71,17 +63,20 @@ func (u *discord) Select() *notifier.Notification {
|
|||
func (u *discord) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue)
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
u.Online = false
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *discord) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name)
|
||||
var msg interface{}
|
||||
if s.UpdateNotify {
|
||||
s.UpdateNotify = false
|
||||
}
|
||||
msg = s.DownText
|
||||
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
|
@ -95,7 +90,7 @@ func (u *discord) OnSave() error {
|
|||
func (u *discord) OnTest() error {
|
||||
outError := errors.New("Incorrect discord URL, please confirm URL is correct")
|
||||
message := `{"content": "Testing the discord notifier"}`
|
||||
contents, _, err := utils.HttpRequest(discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second))
|
||||
contents, _, err := utils.HttpRequest(Discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true)
|
||||
if string(contents) == "" {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ var (
|
|||
|
||||
func init() {
|
||||
DISCORD_URL = os.Getenv("DISCORD_URL")
|
||||
discorder.Host = DISCORD_URL
|
||||
Discorder.Host = DISCORD_URL
|
||||
}
|
||||
|
||||
func TestDiscordNotifier(t *testing.T) {
|
||||
|
@ -42,66 +42,58 @@ func TestDiscordNotifier(t *testing.T) {
|
|||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load discord", func(t *testing.T) {
|
||||
discorder.Host = DISCORD_URL
|
||||
discorder.Delay = time.Duration(100 * time.Millisecond)
|
||||
err := notifier.AddNotifier(discorder)
|
||||
Discorder.Host = DISCORD_URL
|
||||
Discorder.Delay = time.Duration(100 * time.Millisecond)
|
||||
err := notifier.AddNotifiers(Discorder)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", discorder.Author)
|
||||
assert.Equal(t, DISCORD_URL, discorder.Host)
|
||||
})
|
||||
|
||||
t.Run("Load discord Notifier", func(t *testing.T) {
|
||||
notifier.Load()
|
||||
assert.Equal(t, "Hunter Long", Discorder.Author)
|
||||
assert.Equal(t, DISCORD_URL, Discorder.Host)
|
||||
})
|
||||
|
||||
t.Run("discord Notifier Tester", func(t *testing.T) {
|
||||
assert.True(t, discorder.CanTest())
|
||||
assert.True(t, Discorder.CanTest())
|
||||
})
|
||||
|
||||
t.Run("discord Within Limits", func(t *testing.T) {
|
||||
ok, err := discorder.WithinLimits()
|
||||
ok, err := Discorder.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("discord OnFailure", func(t *testing.T) {
|
||||
discorder.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(discorder.Queue))
|
||||
})
|
||||
|
||||
t.Run("discord Check Offline", func(t *testing.T) {
|
||||
assert.False(t, discorder.Online)
|
||||
Discorder.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(Discorder.Queue))
|
||||
})
|
||||
|
||||
t.Run("discord OnSuccess", func(t *testing.T) {
|
||||
discorder.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(discorder.Queue))
|
||||
Discorder.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Discorder.Queue))
|
||||
})
|
||||
|
||||
t.Run("discord Check Back Online", func(t *testing.T) {
|
||||
assert.True(t, discorder.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("discord OnSuccess Again", func(t *testing.T) {
|
||||
discorder.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(discorder.Queue))
|
||||
Discorder.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Discorder.Queue))
|
||||
})
|
||||
|
||||
t.Run("discord Send", func(t *testing.T) {
|
||||
err := discorder.Send(discordMessage)
|
||||
err := Discorder.Send(discordMessage)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("discord Test", func(t *testing.T) {
|
||||
err := discorder.OnTest()
|
||||
err := Discorder.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("discord Queue", func(t *testing.T) {
|
||||
go notifier.Queue(discorder)
|
||||
go notifier.Queue(Discorder)
|
||||
time.Sleep(1 * time.Second)
|
||||
assert.Equal(t, DISCORD_URL, discorder.Host)
|
||||
assert.Equal(t, 0, len(discorder.Queue))
|
||||
assert.Equal(t, DISCORD_URL, Discorder.Host)
|
||||
assert.Equal(t, 0, len(Discorder.Queue))
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"html/template"
|
||||
"net/smtp"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -112,7 +111,7 @@ type email struct {
|
|||
*notifier.Notification
|
||||
}
|
||||
|
||||
var emailer = &email{¬ifier.Notification{
|
||||
var Emailer = &email{¬ifier.Notification{
|
||||
Method: "email",
|
||||
Title: "email",
|
||||
Description: "Send emails via SMTP when services are online or offline.",
|
||||
|
@ -158,13 +157,6 @@ var emailer = &email{¬ifier.Notification{
|
|||
}},
|
||||
}}
|
||||
|
||||
func init() {
|
||||
err := notifier.AddNotifier(emailer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send will send the SMTP email with your authentication It accepts type: *emailOutgoing
|
||||
func (u *email) Send(msg interface{}) error {
|
||||
email := msg.(*emailOutgoing)
|
||||
|
@ -195,23 +187,27 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) {
|
|||
From: u.Var1,
|
||||
}
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), email)
|
||||
u.Online = false
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *email) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
var msg string
|
||||
if s.UpdateNotify {
|
||||
s.UpdateNotify = false
|
||||
}
|
||||
msg = s.DownText
|
||||
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
email := &emailOutgoing{
|
||||
To: u.Var2,
|
||||
Subject: fmt.Sprintf("Service %v is Back Online", s.Name),
|
||||
Subject: msg,
|
||||
Template: mainEmailTemplate,
|
||||
Data: interface{}(s),
|
||||
From: u.Var1,
|
||||
}
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), email)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
func (u *email) Select() *notifier.Notification {
|
||||
|
@ -220,31 +216,13 @@ func (u *email) Select() *notifier.Notification {
|
|||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
func (u *email) OnSave() error {
|
||||
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
|
||||
utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
|
||||
// Do updating stuff here
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnTest triggers when this notifier has been saved
|
||||
func (u *email) OnTest() error {
|
||||
host := fmt.Sprintf("%v:%v", u.Host, u.Port)
|
||||
dial, err := smtp.Dial(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.ApiKey != "true" {
|
||||
err = dial.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if u.Username != "" || u.Password != "" {
|
||||
auth := smtp.PlainAuth("", u.Username, u.Password, host)
|
||||
err = dial.Auth(auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
testService := &types.Service{
|
||||
Id: 1,
|
||||
Name: "Example Service",
|
||||
|
@ -257,21 +235,20 @@ func (u *email) OnTest() error {
|
|||
LastStatusCode: 200,
|
||||
Expected: types.NewNullString("test example"),
|
||||
LastResponse: "<html>this is an example response</html>",
|
||||
CreatedAt: time.Now().Add(-24 * time.Hour),
|
||||
CreatedAt: utils.Now().Add(-24 * time.Hour),
|
||||
}
|
||||
email := &emailOutgoing{
|
||||
To: u.Var2,
|
||||
Subject: fmt.Sprintf("Service %v is Back Online", testService.Name),
|
||||
Template: mainEmailTemplate,
|
||||
Data: interface{}(testService),
|
||||
Data: testService,
|
||||
From: u.Var1,
|
||||
}
|
||||
err = u.Send(email)
|
||||
return err
|
||||
return u.dialSend(email)
|
||||
}
|
||||
|
||||
func (u *email) dialSend(email *emailOutgoing) error {
|
||||
mailer = mail.NewDialer(emailer.Host, emailer.Port, emailer.Username, emailer.Password)
|
||||
mailer = mail.NewDialer(Emailer.Host, Emailer.Port, Emailer.Username, Emailer.Password)
|
||||
emailSource(email)
|
||||
m := mail.NewMessage()
|
||||
// if email setting TLS is Disabled
|
||||
|
@ -285,7 +262,7 @@ func (u *email) dialSend(email *emailOutgoing) error {
|
|||
m.SetHeader("Subject", email.Subject)
|
||||
m.SetBody("text/html", email.Source)
|
||||
if err := mailer.DialAndSend(m); err != nil {
|
||||
utils.Log(3, fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
|
||||
utils.Log.Errorln(fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -300,11 +277,11 @@ func emailTemplate(contents string, data interface{}) string {
|
|||
t := template.New("email")
|
||||
t, err := t.Parse(contents)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
utils.Log.Errorln(err)
|
||||
}
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, data); err != nil {
|
||||
utils.Log(2, err)
|
||||
utils.Log.Warnln(err)
|
||||
}
|
||||
result := tpl.String()
|
||||
return result
|
||||
|
|
|
@ -44,12 +44,12 @@ func init() {
|
|||
EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO")
|
||||
EMAIL_PORT = utils.ToInt(os.Getenv("EMAIL_PORT"))
|
||||
|
||||
emailer.Host = EMAIL_HOST
|
||||
emailer.Username = EMAIL_USER
|
||||
emailer.Password = EMAIL_PASS
|
||||
emailer.Var1 = EMAIL_OUTGOING
|
||||
emailer.Var2 = EMAIL_SEND_TO
|
||||
emailer.Port = int(EMAIL_PORT)
|
||||
Emailer.Host = EMAIL_HOST
|
||||
Emailer.Username = EMAIL_USER
|
||||
Emailer.Password = EMAIL_PASS
|
||||
Emailer.Var1 = EMAIL_OUTGOING
|
||||
Emailer.Var2 = EMAIL_SEND_TO
|
||||
Emailer.Port = int(EMAIL_PORT)
|
||||
}
|
||||
|
||||
func TestEmailNotifier(t *testing.T) {
|
||||
|
@ -61,36 +61,32 @@ func TestEmailNotifier(t *testing.T) {
|
|||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("New Emailer", func(t *testing.T) {
|
||||
emailer.Host = EMAIL_HOST
|
||||
emailer.Username = EMAIL_USER
|
||||
emailer.Password = EMAIL_PASS
|
||||
emailer.Var1 = EMAIL_OUTGOING
|
||||
emailer.Var2 = EMAIL_SEND_TO
|
||||
emailer.Port = int(EMAIL_PORT)
|
||||
emailer.Delay = time.Duration(100 * time.Millisecond)
|
||||
Emailer.Host = EMAIL_HOST
|
||||
Emailer.Username = EMAIL_USER
|
||||
Emailer.Password = EMAIL_PASS
|
||||
Emailer.Var1 = EMAIL_OUTGOING
|
||||
Emailer.Var2 = EMAIL_SEND_TO
|
||||
Emailer.Port = int(EMAIL_PORT)
|
||||
Emailer.Delay = time.Duration(100 * time.Millisecond)
|
||||
|
||||
testEmail = &emailOutgoing{
|
||||
To: emailer.GetValue("var2"),
|
||||
To: Emailer.GetValue("var2"),
|
||||
Subject: fmt.Sprintf("Service %v is Failing", TestService.Name),
|
||||
Template: mainEmailTemplate,
|
||||
Data: TestService,
|
||||
From: emailer.GetValue("var1"),
|
||||
From: Emailer.GetValue("var1"),
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Add email Notifier", func(t *testing.T) {
|
||||
err := notifier.AddNotifier(emailer)
|
||||
err := notifier.AddNotifiers(Emailer)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", emailer.Author)
|
||||
assert.Equal(t, EMAIL_HOST, emailer.Host)
|
||||
})
|
||||
|
||||
t.Run("Emailer Load", func(t *testing.T) {
|
||||
notifier.Load()
|
||||
assert.Equal(t, "Hunter Long", Emailer.Author)
|
||||
assert.Equal(t, EMAIL_HOST, Emailer.Host)
|
||||
})
|
||||
|
||||
t.Run("email Within Limits", func(t *testing.T) {
|
||||
ok, err := emailer.WithinLimits()
|
||||
ok, err := Emailer.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
@ -101,44 +97,40 @@ func TestEmailNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("email OnFailure", func(t *testing.T) {
|
||||
emailer.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(emailer.Queue))
|
||||
})
|
||||
|
||||
t.Run("email Check Offline", func(t *testing.T) {
|
||||
assert.False(t, emailer.Online)
|
||||
Emailer.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(Emailer.Queue))
|
||||
})
|
||||
|
||||
t.Run("email OnSuccess", func(t *testing.T) {
|
||||
emailer.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(emailer.Queue))
|
||||
Emailer.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Emailer.Queue))
|
||||
})
|
||||
|
||||
t.Run("email Check Back Online", func(t *testing.T) {
|
||||
assert.True(t, emailer.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("email OnSuccess Again", func(t *testing.T) {
|
||||
emailer.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(emailer.Queue))
|
||||
Emailer.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Emailer.Queue))
|
||||
})
|
||||
|
||||
t.Run("email Send", func(t *testing.T) {
|
||||
err := emailer.Send(testEmail)
|
||||
err := Emailer.Send(testEmail)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("emailer Test", func(t *testing.T) {
|
||||
t.Run("Emailer Test", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
err := emailer.OnTest()
|
||||
err := Emailer.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("email Run Queue", func(t *testing.T) {
|
||||
go notifier.Queue(emailer)
|
||||
go notifier.Queue(Emailer)
|
||||
time.Sleep(6 * time.Second)
|
||||
assert.Equal(t, EMAIL_HOST, emailer.Host)
|
||||
assert.Equal(t, 0, len(emailer.Queue))
|
||||
assert.Equal(t, EMAIL_HOST, Emailer.Host)
|
||||
assert.Equal(t, 0, len(Emailer.Queue))
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ type lineNotifier struct {
|
|||
*notifier.Notification
|
||||
}
|
||||
|
||||
var lineNotify = &lineNotifier{¬ifier.Notification{
|
||||
var LineNotify = &lineNotifier{¬ifier.Notification{
|
||||
Method: lineNotifyMethod,
|
||||
Title: "LINE Notify",
|
||||
Description: "LINE Notify will send notifications to your LINE Notify account when services are offline or online. Based on the <a href=\"https://notify-bot.line.me/doc/en/\">LINE Notify API</a>.",
|
||||
|
@ -48,21 +48,13 @@ var lineNotify = &lineNotifier{¬ifier.Notification{
|
|||
}}},
|
||||
}
|
||||
|
||||
// DEFINE YOUR NOTIFICATION HERE.
|
||||
func init() {
|
||||
err := notifier.AddNotifier(lineNotify)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send will send a HTTP Post with the Authorization to the notify-api.line.me server. It accepts type: string
|
||||
func (u *lineNotifier) Send(msg interface{}) error {
|
||||
message := msg.(string)
|
||||
v := url.Values{}
|
||||
v.Set("message", message)
|
||||
headers := []string{fmt.Sprintf("Authorization=Bearer %v", u.ApiSecret)}
|
||||
_, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second))
|
||||
_, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -74,22 +66,26 @@ func (u *lineNotifier) Select() *notifier.Notification {
|
|||
func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
u.Online = false
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *lineNotifier) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
var msg string
|
||||
if s.UpdateNotify {
|
||||
s.UpdateNotify = false
|
||||
}
|
||||
msg = s.DownText
|
||||
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
func (u *lineNotifier) OnSave() error {
|
||||
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
|
||||
// Do updating stuff here
|
||||
msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method)
|
||||
utils.Log.Infoln(msg)
|
||||
u.AddQueue("saved", msg)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -31,10 +31,10 @@ type mobilePush struct {
|
|||
*notifier.Notification
|
||||
}
|
||||
|
||||
var mobile = &mobilePush{¬ifier.Notification{
|
||||
var Mobile = &mobilePush{¬ifier.Notification{
|
||||
Method: "mobile",
|
||||
Title: "Mobile Notifications",
|
||||
Description: `Receive push notifications on your mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the mobile app setup in seconds.
|
||||
Description: `Receive push notifications on your Mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the Mobile app setup in seconds.
|
||||
<p align="center"><a href="https://play.google.com/store/apps/details?id=com.statping"><img src="https://img.cjx.io/google-play.svg"></a><a href="https://itunes.apple.com/us/app/apple-store/id1445513219"><img src="https://img.cjx.io/app-store-badge.svg"></a></p>`,
|
||||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
|
@ -43,7 +43,7 @@ var mobile = &mobilePush{¬ifier.Notification{
|
|||
Form: []notifier.NotificationForm{{
|
||||
Type: "text",
|
||||
Title: "Device Identifiers",
|
||||
Placeholder: "A list of your mobile device push notification ID's.",
|
||||
Placeholder: "A list of your Mobile device push notification ID's.",
|
||||
DbField: "var1",
|
||||
IsHidden: true,
|
||||
}, {
|
||||
|
@ -55,14 +55,6 @@ var mobile = &mobilePush{¬ifier.Notification{
|
|||
}}},
|
||||
}
|
||||
|
||||
// init the discord notifier
|
||||
func init() {
|
||||
err := notifier.AddNotifier(mobile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *mobilePush) Select() *notifier.Notification {
|
||||
return u.Notification
|
||||
}
|
||||
|
@ -100,33 +92,31 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) {
|
|||
Data: data,
|
||||
}
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
u.Online = false
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *mobilePush) OnSuccess(s *types.Service) {
|
||||
data := dataJson(s, nil)
|
||||
if !u.Online {
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
var msgStr string
|
||||
if s.UpdateNotify {
|
||||
s.UpdateNotify = false
|
||||
}
|
||||
msgStr = s.DownText
|
||||
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
msg := &pushArray{
|
||||
Message: fmt.Sprintf("Your service '%v' is back online!", s.Name),
|
||||
Message: msgStr,
|
||||
Title: "Service Online",
|
||||
Topic: mobileIdentifier,
|
||||
Data: data,
|
||||
}
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
func (u *mobilePush) OnSave() error {
|
||||
msg := &pushArray{
|
||||
Message: "The Mobile Notifier has been saved",
|
||||
Title: "Notification Saved",
|
||||
Topic: mobileIdentifier,
|
||||
}
|
||||
u.AddQueue("saved", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -178,7 +168,7 @@ func pushRequest(msg *pushArray) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
url := "https://push.statping.com/api/push"
|
||||
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second))
|
||||
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second), true)
|
||||
return body, err
|
||||
}
|
||||
|
||||
|
|
|
@ -31,99 +31,79 @@ var (
|
|||
func init() {
|
||||
MOBILE_ID = os.Getenv("MOBILE_ID")
|
||||
MOBILE_NUMBER = os.Getenv("MOBILE_NUMBER")
|
||||
mobile.Var1 = MOBILE_ID
|
||||
Mobile.Var1 = MOBILE_ID
|
||||
}
|
||||
|
||||
func TestMobileNotifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
mobile.Var1 = MOBILE_ID
|
||||
mobile.Var2 = os.Getenv("MOBILE_NUMBER")
|
||||
Mobile.Var1 = MOBILE_ID
|
||||
Mobile.Var2 = os.Getenv("MOBILE_NUMBER")
|
||||
if MOBILE_ID == "" {
|
||||
t.Log("mobile notifier testing skipped, missing MOBILE_ID environment variable")
|
||||
t.Log("Mobile notifier testing skipped, missing MOBILE_ID environment variable")
|
||||
t.SkipNow()
|
||||
}
|
||||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load mobile", func(t *testing.T) {
|
||||
mobile.Var1 = MOBILE_ID
|
||||
mobile.Var2 = MOBILE_NUMBER
|
||||
mobile.Delay = time.Duration(100 * time.Millisecond)
|
||||
mobile.Limits = 10
|
||||
err := notifier.AddNotifier(mobile)
|
||||
t.Run("Load Mobile", func(t *testing.T) {
|
||||
Mobile.Var1 = MOBILE_ID
|
||||
Mobile.Var2 = MOBILE_NUMBER
|
||||
Mobile.Delay = time.Duration(100 * time.Millisecond)
|
||||
Mobile.Limits = 10
|
||||
err := notifier.AddNotifiers(Mobile)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", mobile.Author)
|
||||
assert.Equal(t, MOBILE_ID, mobile.Var1)
|
||||
assert.Equal(t, MOBILE_NUMBER, mobile.Var2)
|
||||
assert.Equal(t, "Hunter Long", Mobile.Author)
|
||||
assert.Equal(t, MOBILE_ID, Mobile.Var1)
|
||||
assert.Equal(t, MOBILE_NUMBER, Mobile.Var2)
|
||||
})
|
||||
|
||||
t.Run("Load mobile Notifier", func(t *testing.T) {
|
||||
notifier.Load()
|
||||
t.Run("Mobile Notifier Tester", func(t *testing.T) {
|
||||
assert.True(t, Mobile.CanTest())
|
||||
})
|
||||
|
||||
t.Run("mobile Notifier Tester", func(t *testing.T) {
|
||||
assert.True(t, mobile.CanTest())
|
||||
})
|
||||
|
||||
t.Run("mobile Within Limits", func(t *testing.T) {
|
||||
ok, err := mobile.WithinLimits()
|
||||
t.Run("Mobile Within Limits", func(t *testing.T) {
|
||||
ok, err := Mobile.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("mobile OnFailure", func(t *testing.T) {
|
||||
mobile.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(mobile.Queue))
|
||||
t.Run("Mobile OnFailure", func(t *testing.T) {
|
||||
Mobile.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(Mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile OnFailure multiple times", func(t *testing.T) {
|
||||
for i := 0; i <= 50; i++ {
|
||||
mobile.OnFailure(TestService, TestFailure)
|
||||
}
|
||||
assert.Equal(t, 52, len(mobile.Queue))
|
||||
t.Run("Mobile OnSuccess", func(t *testing.T) {
|
||||
Mobile.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile Check Offline", func(t *testing.T) {
|
||||
assert.False(t, mobile.Online)
|
||||
})
|
||||
|
||||
t.Run("mobile OnSuccess", func(t *testing.T) {
|
||||
mobile.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile Queue after being online", func(t *testing.T) {
|
||||
assert.True(t, mobile.Online)
|
||||
assert.Equal(t, 1, len(mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile OnSuccess Again", func(t *testing.T) {
|
||||
t.Run("Mobile OnSuccess Again", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
assert.True(t, mobile.Online)
|
||||
mobile.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(mobile.Queue))
|
||||
go notifier.Queue(mobile)
|
||||
assert.True(t, TestService.Online)
|
||||
Mobile.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Mobile.Queue))
|
||||
go notifier.Queue(Mobile)
|
||||
time.Sleep(20 * time.Second)
|
||||
assert.Equal(t, 1, len(mobile.Queue))
|
||||
assert.Equal(t, 1, len(Mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile Within Limits again", func(t *testing.T) {
|
||||
ok, err := mobile.WithinLimits()
|
||||
t.Run("Mobile Within Limits again", func(t *testing.T) {
|
||||
ok, err := Mobile.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("mobile Test", func(t *testing.T) {
|
||||
t.Run("Mobile Test", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
err := mobile.OnTest()
|
||||
err := Mobile.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("mobile Queue", func(t *testing.T) {
|
||||
t.Run("Mobile Queue", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
go notifier.Queue(mobile)
|
||||
go notifier.Queue(Mobile)
|
||||
time.Sleep(15 * time.Second)
|
||||
assert.Equal(t, MOBILE_ID, mobile.Var1)
|
||||
assert.Equal(t, 0, len(mobile.Queue))
|
||||
assert.Equal(t, MOBILE_ID, Mobile.Var1)
|
||||
assert.Equal(t, 0, len(Mobile.Queue))
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -42,14 +42,15 @@ var TestService = &types.Service{
|
|||
Method: "GET",
|
||||
Timeout: 20,
|
||||
LastStatusCode: 404,
|
||||
Online: true,
|
||||
LastResponse: "<html>this is an example response</html>",
|
||||
CreatedAt: time.Now().Add(-24 * time.Hour),
|
||||
CreatedAt: utils.Now().Add(-24 * time.Hour),
|
||||
}
|
||||
|
||||
var TestFailure = &types.Failure{
|
||||
Issue: "testing",
|
||||
Service: 1,
|
||||
CreatedAt: time.Now().Add(-12 * time.Hour),
|
||||
CreatedAt: utils.Now().Add(-12 * time.Hour),
|
||||
}
|
||||
|
||||
var TestUser = &types.User{
|
||||
|
@ -73,8 +74,8 @@ func init() {
|
|||
}
|
||||
|
||||
func injectDatabase() {
|
||||
utils.DeleteFile(dir + "/statup.db")
|
||||
db, err := gorm.Open("sqlite3", dir+"/statup.db")
|
||||
utils.DeleteFile(dir + "/notifiers.db")
|
||||
db, err := gorm.Open("sqlite3", dir+"/notifiers.db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -29,23 +29,16 @@ import (
|
|||
|
||||
const (
|
||||
slackMethod = "slack"
|
||||
failingTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> has just received a Failure notification based on your expected results. {{.Service.Name}} responded with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected Status Code", "value": "{{.Service.ExpectedStatus}}", "short": true }, { "title": "Received Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#FF0000", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
|
||||
failingTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> has just received a Failure notification based on your expected results. {{.Service.Name}} responded with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected Status Code", "value": "{{.Service.ExpectedStatus}}", "short": true }, { "title": "Received Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ,{ "title": "Error Message", "value": "{{.Issue}}", "short": false } ], "color": "#FF0000", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
|
||||
successTemplate = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "Your Statping service <{{.Service.Domain}}|{{.Service.Name}}> is now back online and meets your expected responses.", "color": "#00FF00", "thumb_url": "https://statping.com", "footer": "Statping", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
|
||||
slackText = `{"text":"{{.}}"}`
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := notifier.AddNotifier(slacker)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type slack struct {
|
||||
*notifier.Notification
|
||||
}
|
||||
|
||||
var slacker = &slack{¬ifier.Notification{
|
||||
var Slacker = &slack{¬ifier.Notification{
|
||||
Method: slackMethod,
|
||||
Title: "slack",
|
||||
Description: "Send notifications to your slack channel when a service is offline. Insert your Incoming webhooker URL for your channel to receive notifications. Based on the <a href=\"https://api.slack.com/incoming-webhooks\">slack API</a>.",
|
||||
|
@ -57,7 +50,7 @@ var slacker = &slack{¬ifier.Notification{
|
|||
Form: []notifier.NotificationForm{{
|
||||
Type: "text",
|
||||
Title: "Incoming webhooker Url",
|
||||
Placeholder: "Insert your slack webhook URL here.",
|
||||
Placeholder: "Insert your slack Webhook URL here.",
|
||||
SmallText: "Incoming webhooker URL from <a href=\"https://api.slack.com/apps\" target=\"_blank\">slack Apps</a>",
|
||||
DbField: "Host",
|
||||
Required: true,
|
||||
|
@ -71,7 +64,7 @@ func parseSlackMessage(id int64, temp string, data interface{}) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slacker.AddQueue(fmt.Sprintf("service_%v", id), buf.String())
|
||||
Slacker.AddQueue(fmt.Sprintf("service_%v", id), buf.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -79,12 +72,13 @@ type slackMessage struct {
|
|||
Service *types.Service
|
||||
Template string
|
||||
Time int64
|
||||
Issue string
|
||||
}
|
||||
|
||||
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string
|
||||
func (u *slack) Send(msg interface{}) error {
|
||||
message := msg.(string)
|
||||
_, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second))
|
||||
_, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -93,7 +87,7 @@ func (u *slack) Select() *notifier.Notification {
|
|||
}
|
||||
|
||||
func (u *slack) OnTest() error {
|
||||
contents, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second))
|
||||
contents, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second), true)
|
||||
if string(contents) != "ok" {
|
||||
return errors.New("The slack response was incorrect, check the URL")
|
||||
}
|
||||
|
@ -105,24 +99,23 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
|
|||
message := slackMessage{
|
||||
Service: s,
|
||||
Template: failingTemplate,
|
||||
Time: time.Now().Unix(),
|
||||
Time: utils.Now().Unix(),
|
||||
Issue: f.Issue,
|
||||
}
|
||||
parseSlackMessage(s.Id, failingTemplate, message)
|
||||
u.Online = false
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *slack) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
if !s.Online {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
message := slackMessage{
|
||||
Service: s,
|
||||
Template: successTemplate,
|
||||
Time: time.Now().Unix(),
|
||||
Time: utils.Now().Unix(),
|
||||
}
|
||||
parseSlackMessage(s.Id, successTemplate, message)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
|
|
|
@ -30,13 +30,13 @@ var (
|
|||
|
||||
func init() {
|
||||
SLACK_URL = os.Getenv("SLACK_URL")
|
||||
slacker.Host = SLACK_URL
|
||||
Slacker.Host = SLACK_URL
|
||||
}
|
||||
|
||||
func TestSlackNotifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
SLACK_URL = os.Getenv("SLACK_URL")
|
||||
slacker.Host = SLACK_URL
|
||||
Slacker.Host = SLACK_URL
|
||||
if SLACK_URL == "" {
|
||||
t.Log("slack notifier testing skipped, missing SLACK_URL environment variable")
|
||||
t.SkipNow()
|
||||
|
@ -44,92 +44,72 @@ func TestSlackNotifier(t *testing.T) {
|
|||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load slack", func(t *testing.T) {
|
||||
slacker.Host = SLACK_URL
|
||||
slacker.Delay = time.Duration(100 * time.Millisecond)
|
||||
slacker.Limits = 3
|
||||
err := notifier.AddNotifier(slacker)
|
||||
Slacker.Host = SLACK_URL
|
||||
Slacker.Delay = time.Duration(100 * time.Millisecond)
|
||||
Slacker.Limits = 3
|
||||
err := notifier.AddNotifiers(Slacker)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", slacker.Author)
|
||||
assert.Equal(t, SLACK_URL, slacker.Host)
|
||||
})
|
||||
|
||||
t.Run("Load slack Notifier", func(t *testing.T) {
|
||||
notifier.Load()
|
||||
assert.Equal(t, "Hunter Long", Slacker.Author)
|
||||
assert.Equal(t, SLACK_URL, Slacker.Host)
|
||||
})
|
||||
|
||||
t.Run("slack Notifier Tester", func(t *testing.T) {
|
||||
assert.True(t, slacker.CanTest())
|
||||
assert.True(t, Slacker.CanTest())
|
||||
})
|
||||
|
||||
//t.Run("slack parse message", func(t *testing.T) {
|
||||
// err := parseSlackMessage(slackText, "this is a test!")
|
||||
// assert.Nil(t, err)
|
||||
// assert.Equal(t, 1, len(slacker.Queue))
|
||||
// assert.Equal(t, 1, len(Slacker.Queue))
|
||||
//})
|
||||
|
||||
t.Run("slack Within Limits", func(t *testing.T) {
|
||||
ok, err := slacker.WithinLimits()
|
||||
ok, err := Slacker.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("slack OnFailure", func(t *testing.T) {
|
||||
slacker.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack OnFailure multiple times", func(t *testing.T) {
|
||||
for i := 0; i <= 50; i++ {
|
||||
slacker.OnFailure(TestService, TestFailure)
|
||||
}
|
||||
assert.Equal(t, 52, len(slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack Check Offline", func(t *testing.T) {
|
||||
assert.False(t, slacker.Online)
|
||||
Slacker.OnFailure(TestService, TestFailure)
|
||||
assert.Equal(t, 1, len(Slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack OnSuccess", func(t *testing.T) {
|
||||
slacker.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack Queue after being online", func(t *testing.T) {
|
||||
assert.True(t, slacker.Online)
|
||||
assert.Equal(t, 1, len(slacker.Queue))
|
||||
Slacker.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack OnSuccess Again", func(t *testing.T) {
|
||||
assert.True(t, slacker.Online)
|
||||
slacker.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(slacker.Queue))
|
||||
go notifier.Queue(slacker)
|
||||
time.Sleep(6 * time.Second)
|
||||
assert.Equal(t, 0, len(slacker.Queue))
|
||||
assert.True(t, TestService.Online)
|
||||
Slacker.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(Slacker.Queue))
|
||||
go notifier.Queue(Slacker)
|
||||
time.Sleep(15 * time.Second)
|
||||
assert.Equal(t, 0, len(Slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack Within Limits again", func(t *testing.T) {
|
||||
ok, err := slacker.WithinLimits()
|
||||
ok, err := Slacker.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("slack Send", func(t *testing.T) {
|
||||
err := slacker.Send(slackTestMessage)
|
||||
err := Slacker.Send(slackTestMessage)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(slacker.Queue))
|
||||
assert.Equal(t, 0, len(Slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("slack Test", func(t *testing.T) {
|
||||
err := slacker.OnTest()
|
||||
err := Slacker.OnTest()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("slack Queue", func(t *testing.T) {
|
||||
go notifier.Queue(slacker)
|
||||
time.Sleep(5 * time.Second)
|
||||
assert.Equal(t, SLACK_URL, slacker.Host)
|
||||
assert.Equal(t, 0, len(slacker.Queue))
|
||||
go notifier.Queue(Slacker)
|
||||
time.Sleep(10 * time.Second)
|
||||
assert.Equal(t, SLACK_URL, Slacker.Host)
|
||||
assert.Equal(t, 0, len(Slacker.Queue))
|
||||
})
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue