mirror of https://github.com/statping/statping
Merge branch 'master' into patch-1
commit
ef0d9594b6
|
@ -2,10 +2,7 @@ os:
|
|||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.1"
|
||||
|
||||
go: 1.13
|
||||
go_import_path: github.com/hunterlong/statping
|
||||
|
||||
cache:
|
||||
|
|
17
Dockerfile
17
Dockerfile
|
@ -1,27 +1,28 @@
|
|||
FROM golang:1.11-alpine as base
|
||||
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
|
||||
FROM golang:1.12-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 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 . /go/src/github.com/hunterlong/statping
|
||||
ADD Makefile Gopkg.* /go/src/github.com/hunterlong/statping/
|
||||
RUN make dep && \
|
||||
make dev-deps && \
|
||||
make install
|
||||
make dev-deps
|
||||
ADD . /go/src/github.com/hunterlong/statping
|
||||
RUN make install
|
||||
|
||||
# Statping :latest Docker Image
|
||||
FROM alpine:latest
|
||||
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
|
||||
ARG VERSION
|
||||
ENV IS_DOCKER=true
|
||||
ENV STATPING_DIR=/app
|
||||
ENV PORT=8080
|
||||
RUN apk --no-cache add curl jq
|
||||
RUN apk --no-cache add curl jq libsass
|
||||
|
||||
COPY --from=base /usr/local/bin/sass /usr/local/bin/sass
|
||||
COPY --from=base /go/bin/statping /usr/local/bin/statping
|
||||
|
|
|
@ -2,15 +2,36 @@
|
|||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6212ce7f839b40213d1ff75c3df7816c4023faf4d29da630ce1dfdd1ccd5fb02"
|
||||
digest = "1:d4bfd57449b0bdfe927ec45c8463afd8f5b6012d4bcd5a9da8581a408c23e57c"
|
||||
name = "github.com/99designs/gqlgen"
|
||||
packages = [
|
||||
"complexity",
|
||||
"graphql",
|
||||
"graphql/introspection",
|
||||
"handler",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a7bc468ca1b184a5ce1b07ea331e0121fc56ae82"
|
||||
version = "v0.9.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:07f7314344b2771963ada0b2a4a426c59d782dac227dcfff2499188a186446c0"
|
||||
name = "github.com/GeertJohan/go.rice"
|
||||
packages = [
|
||||
".",
|
||||
"embedded",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "0af3f3b09a0a8b391f63ab52ba5ab50f84fabd30"
|
||||
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"
|
||||
|
@ -21,12 +42,12 @@
|
|||
version = "0.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5fd5c4d4282935b7a575299494f2c09e9d2cacded7815c83aff7c1602aff3154"
|
||||
digest = "1:8e8da6cc8cca12851d4e089d970a0f7387b3a6bcc8c459ff432213b03076a66d"
|
||||
name = "github.com/daaku/go.zipexe"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a5fe2436ffcb3236e175e5149162b41cd28bd27d"
|
||||
revision = "74d766ac1dde7458348221869a7d1e7e5fa0597e"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
|
||||
|
@ -61,20 +82,12 @@
|
|||
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:ca59b1175189b3f0e9f1793d2c350114be36eaabbe5b9f554b35edee1de50aea"
|
||||
digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a7962380ca08b5a188038c69871b8d3fbdf31e89"
|
||||
version = "v1.7.0"
|
||||
revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15"
|
||||
version = "v1.7.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e72d1ebb8d395cf9f346fd9cbc652e5ae222dd85e0ac842dc57f175abed6d195"
|
||||
|
@ -85,15 +98,34 @@
|
|||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e5bf52fd66a2e984b57b4c0f2c4ee024ed749a19886246240629998dc0cf31ce"
|
||||
digest = "1:172c862eabc72e90f461bcef223c49869628bec6d989386dfb03281ae3222148"
|
||||
name = "github.com/gorilla/sessions"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f57b7e2d29c6211d16ffa52a0998272f75799030"
|
||||
version = "v1.1.3"
|
||||
revision = "4355a998706e83fe1d71c31b07af94e34f68d74a"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ff312c4d510c67954a6fc6a11c9ff72a2b2169584776b7419c7b8c729e2b13ac"
|
||||
digest = "1:e62657cca9badaa308d86e7716083e4c5933bb78e30a17743fc67f50be26f6f4"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c3e18be99d19e6b3e8f1559eea2c161a665c4b6b"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c77361e611524ec8f2ad37c408c3c916111a70b6acf806a1200855696bf8fa4d"
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = [
|
||||
".",
|
||||
"simplelru",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "7f827b33c0f158ec5dfbba01bb0b14a4541fd81d"
|
||||
version = "v0.5.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b0c1770be8c52cf00117b98049de1e4df91c8df588102198364b09669bb60178"
|
||||
name = "github.com/jinzhu/gorm"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -102,16 +134,16 @@
|
|||
"dialects/sqlite",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "472c70caa40267cb89fd8facb07fe6454b578626"
|
||||
version = "v1.9.2"
|
||||
revision = "836fb2c19d84dac7b0272958dfb9af7cf0d0ade4"
|
||||
version = "v1.9.10"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fd97437fbb6b7dce04132cf06775bd258cce305c44add58eb55ca86c6c325160"
|
||||
digest = "1:01ed62f8f4f574d8aff1d88caee113700a2b44c42351943fa73cc1808f736a50"
|
||||
name = "github.com/jinzhu/inflection"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "04140366298a54a039076d798123ffa108fff46c"
|
||||
revision = "f5c5f50e6090ae76a29240b61ae2a90dd810112e"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b"
|
||||
|
@ -122,24 +154,25 @@
|
|||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b18ffc558326ebaed3b4a175617f1e12ed4e3f53d6ebfe5ba372a3de16d22278"
|
||||
digest = "1:0ead8e64fe356bd9221605e3ec40b4438509868018cbbbaaaff3ebae1b69b78b"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"hstore",
|
||||
"oid",
|
||||
"scram",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4ded0e9383f75c197b3a2aaa6d590ac52df6fd79"
|
||||
version = "v1.0.0"
|
||||
revision = "3427c32cb71afc948325f299f040e53c1dd78979"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4a49346ca45376a2bba679ca0e83bec949d780d4e927931317904bad482943ec"
|
||||
digest = "1:79e87abf06b873987dee86598950f5b51732ac454d5a5cab6445a14330e6c9e3"
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c7c4067b79cc51e6dfdcef5c702e74b1e0fa7c75"
|
||||
version = "v1.10.0"
|
||||
revision = "b612a2feea6aa87c6d052d9086572551df06497e"
|
||||
version = "v1.11.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
|
@ -166,31 +199,81 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759"
|
||||
digest = "1:8548c309c65a85933a625be5e7d52b6ac927ca30c56869fae58123b8a77a75e1"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "UT"
|
||||
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
|
||||
version = "v1.3.0"
|
||||
revision = "221dbe5ed46703ee255b1da0dec05086f5035f62"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:1ecf2a49df33be51e757d0033d5d51d5f784f35f68e5a38f797b2d3f03357d71"
|
||||
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 = "b8fe1690c61389d7d2a8074a507d1d40c5d30448"
|
||||
revision = "9756ffdc24725223350eb3266ffb92590d28f278"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:caffb9a4f8c756941de4b3eb577abd167e7fd4b570f2078c05ceb8835a1514cb"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"bpf",
|
||||
"icmp",
|
||||
"internal/iana",
|
||||
"internal/socket",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ba9fcec4b297b415637633c5a6e8fa592e4a16c3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d94059c196c160bd1c4030d49ffaa39a456be516501e5916bea663f5d79a75ec"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "9109b7679e13aa34a54834cfb4949cac4b96e576"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["cloudsql"]
|
||||
pruneopts = "UT"
|
||||
revision = "e9657d882bb81064595ca3b56cbe2546bbabf7b1"
|
||||
version = "v1.4.0"
|
||||
revision = "5f2a59506353b8d5ba8cbbcd9f3c1f41f1eaf079"
|
||||
version = "v1.6.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
|
@ -216,10 +299,21 @@
|
|||
revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135"
|
||||
version = "v2.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
||||
version = "v2.2.2"
|
||||
|
||||
[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",
|
||||
|
@ -234,6 +328,9 @@
|
|||
"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",
|
||||
|
|
22
Gopkg.toml
22
Gopkg.toml
|
@ -26,8 +26,12 @@
|
|||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/99designs/gqlgen"
|
||||
version = "0.9.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/GeertJohan/go.rice"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ararog/timeago"
|
||||
|
@ -43,15 +47,15 @@
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.7.0"
|
||||
version = "1.7.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/sessions"
|
||||
version = "1.1.3"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/jinzhu/gorm"
|
||||
version = "1.9.2"
|
||||
version = "1.9.10"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/joho/godotenv"
|
||||
|
@ -63,7 +67,15 @@
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/tatsushid/go-fastping"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/vektah/gqlparser"
|
||||
version = "1.1.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
|
|
13
Makefile
13
Makefile
|
@ -3,16 +3,17 @@ SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278
|
|||
BINARY_NAME=statping
|
||||
GOPATH:=$(GOPATH)
|
||||
GOCMD=go
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOBUILD=$(GOCMD) build -a
|
||||
GOTEST=$(GOCMD) test
|
||||
GOGET=$(GOCMD) get
|
||||
GOVERSION=1.12.x
|
||||
GOINSTALL=$(GOCMD) install
|
||||
XGO=GOPATH=$(GOPATH) xgo -go 1.11 --dest=build
|
||||
XGO=GOPATH=$(GOPATH) 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)
|
||||
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": [ "1.10.x" ], "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}" }, "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" ] } } }'
|
||||
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statping
|
||||
PATH:=$(PATH)
|
||||
|
||||
|
@ -90,7 +91,7 @@ test: clean compile install build-plugin
|
|||
|
||||
test-api:
|
||||
DB_CONN=sqlite DB_HOST=localhost DB_DATABASE=sqlite DB_PASS=none DB_USER=none statping &
|
||||
sleep 15 && newman run source/tmpl/postman.json -e dev/postman_environment.json --delay-request 500
|
||||
sleep 300 && newman run source/tmpl/postman.json -e dev/postman_environment.json --delay-request 500
|
||||
|
||||
# report coverage to Coveralls
|
||||
coverage:
|
||||
|
@ -156,7 +157,7 @@ docker-build-dev:
|
|||
|
||||
# build Cypress UI testing :cypress docker tag
|
||||
docker-build-cypress: clean
|
||||
GOPATH=$(GOPATH) xgo -out statping -go 1.10.x -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" --targets=linux/amd64 ./cmd
|
||||
GOPATH=$(GOPATH) xgo -out statping -go $(GOVERSION) -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" --targets=linux/amd64 ./cmd
|
||||
docker build -t hunterlong/statping:cypress -f dev/Dockerfile-cypress .
|
||||
rm -f statping
|
||||
|
||||
|
@ -231,6 +232,7 @@ dev-deps:
|
|||
$(GOGET) github.com/ararog/timeago
|
||||
$(GOGET) gopkg.in/natefinch/lumberjack.v2
|
||||
$(GOGET) golang.org/x/crypto/bcrypt
|
||||
$(GOGET) github.com/99designs/gqlgen/...
|
||||
|
||||
# remove files for a clean compile/build
|
||||
clean:
|
||||
|
@ -260,6 +262,7 @@ tag:
|
|||
|
||||
generate:
|
||||
cd source && go generate
|
||||
cd handlers/graphql && go generate
|
||||
|
||||
# compress built binaries into tar.gz and zip formats
|
||||
compress:
|
||||
|
|
|
@ -163,3 +163,10 @@ Statping accepts Push Requests! Feel free to add your own features and notifiers
|
|||
[![Go Report Card](https://goreportcard.com/badge/github.com/hunterlong/statping)](https://goreportcard.com/report/github.com/hunterlong/statping)
|
||||
[![Build Status](https://travis-ci.com/hunterlong/statping.svg?branch=master)](https://travis-ci.com/hunterlong/statping) [![Cypress.io tests](https://img.shields.io/badge/cypress.io-tests-green.svg?style=flat-square)](https://dashboard.cypress.io/#/projects/bi8mhr/runs)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/) [![Godoc](https://godoc.org/github.com/hunterlong/statping?status.svg)](https://godoc.org/github.com/hunterlong/statping)[![Coverage Status](https://coveralls.io/repos/github/hunterlong/statping/badge.svg?branch=master)](https://coveralls.io/github/hunterlong/statping?branch=master)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.buymeacoffee.com/hunterlong" target="_blank">
|
||||
<img height="55" src="https://img.cjx.io/buy-me-redbull.png" >
|
||||
</a>
|
||||
</p>
|
||||
|
|
|
@ -218,6 +218,7 @@ func HelpEcho() {
|
|||
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(" 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)")
|
||||
fmt.Println(" DB_HOST - Database hostname or IP address")
|
||||
fmt.Println(" DB_USER - Database username")
|
||||
|
@ -240,7 +241,7 @@ func HelpEcho() {
|
|||
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(10*time.Second), true)
|
||||
if err != nil {
|
||||
return githubResponse{}, err
|
||||
}
|
||||
|
|
|
@ -33,13 +33,15 @@ var (
|
|||
|
||||
func init() {
|
||||
dir = utils.Directory
|
||||
core.SampleHits = 480
|
||||
}
|
||||
|
||||
func TestStartServerCommand(t *testing.T) {
|
||||
t.SkipNow()
|
||||
os.Setenv("DB_CONN", "sqlite")
|
||||
cmd := helperCommand(nil, "")
|
||||
var got = make(chan string)
|
||||
commandAndSleep(cmd, time.Duration(8*time.Second), got)
|
||||
commandAndSleep(cmd, time.Duration(60*time.Second), got)
|
||||
os.Unsetenv("DB_CONN")
|
||||
gg, _ := <-got
|
||||
assert.Contains(t, gg, "DB_CONN environment variable was found")
|
||||
|
@ -61,6 +63,7 @@ func TestHelpCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStaticCommand(t *testing.T) {
|
||||
t.SkipNow()
|
||||
cmd := helperCommand(nil, "static")
|
||||
var got = make(chan string)
|
||||
commandAndSleep(cmd, time.Duration(10*time.Second), got)
|
||||
|
@ -72,6 +75,7 @@ func TestStaticCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestExportCommand(t *testing.T) {
|
||||
t.SkipNow()
|
||||
cmd := helperCommand(nil, "export")
|
||||
var got = make(chan string)
|
||||
commandAndSleep(cmd, time.Duration(10*time.Second), got)
|
||||
|
@ -99,6 +103,7 @@ func TestAssetsCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunCommand(t *testing.T) {
|
||||
t.SkipNow()
|
||||
cmd := helperCommand(nil, "run")
|
||||
var got = make(chan string)
|
||||
commandAndSleep(cmd, time.Duration(15*time.Second), got)
|
||||
|
@ -120,8 +125,8 @@ func TestVersionCLI(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAssetsCLI(t *testing.T) {
|
||||
run := catchCLI([]string{"assets"})
|
||||
assert.EqualError(t, run, "end")
|
||||
catchCLI([]string{"assets"})
|
||||
//assert.EqualError(t, run, "end")
|
||||
assert.FileExists(t, dir+"/assets/css/base.css")
|
||||
assert.FileExists(t, dir+"/assets/scss/base.scss")
|
||||
}
|
||||
|
@ -153,6 +158,7 @@ func TestHelpCLI(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunOnceCLI(t *testing.T) {
|
||||
t.SkipNow()
|
||||
run := catchCLI([]string{"run"})
|
||||
assert.EqualError(t, run, "end")
|
||||
}
|
||||
|
|
|
@ -65,7 +65,10 @@ func main() {
|
|||
parseFlags()
|
||||
loadDotEnvs()
|
||||
source.Assets()
|
||||
utils.InitLogs()
|
||||
if err := utils.InitLogs(); err != nil {
|
||||
fmt.Printf("Statping Log Error: \n %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) >= 1 {
|
||||
|
|
105
core/checker.go
105
core/checker.go
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/tatsushid/go-fastping"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -38,6 +39,19 @@ 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":
|
||||
s.checkHttp(record)
|
||||
case "tcp", "udp":
|
||||
s.checkTcp(record)
|
||||
case "icmp":
|
||||
s.checkIcmp(record)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckQueue is the main go routine for checking a service
|
||||
func (s *Service) CheckQueue(record bool) {
|
||||
s.Checkpoint = time.Now()
|
||||
|
@ -77,17 +91,11 @@ func (s *Service) parseHost() string {
|
|||
if s.Type == "tcp" || s.Type == "udp" {
|
||||
return s.Domain
|
||||
} else {
|
||||
domain := s.Domain
|
||||
hasPort, _ := regexp.MatchString(`\:([0-9]+)`, domain)
|
||||
if hasPort {
|
||||
splitDomain := strings.Split(s.Domain, ":")
|
||||
domain = splitDomain[len(splitDomain)-2]
|
||||
}
|
||||
host, err := url.Parse(domain)
|
||||
u, err := url.Parse(s.Domain)
|
||||
if err != nil {
|
||||
return s.Domain
|
||||
}
|
||||
return host.Host
|
||||
return strings.Split(u.Host, ":")[0]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +117,36 @@ func (s *Service) dnsCheck() (float64, error) {
|
|||
return subTime, err
|
||||
}
|
||||
|
||||
func isIPv6(address string) bool {
|
||||
return strings.Count(address, ":") >= 2
|
||||
}
|
||||
|
||||
// checkIcmp will send a ICMP ping packet to the service
|
||||
func (s *Service) checkIcmp(record bool) *Service {
|
||||
p := fastping.NewPinger()
|
||||
resolveIP := "ip4:icmp"
|
||||
if isIPv6(s.Domain) {
|
||||
resolveIP = "ip6:icmp"
|
||||
}
|
||||
ra, err := net.ResolveIPAddr(resolveIP, s.Domain)
|
||||
if err != nil {
|
||||
recordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err))
|
||||
return s
|
||||
}
|
||||
p.AddIPAddr(ra)
|
||||
p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
|
||||
s.Latency = rtt.Seconds()
|
||||
recordSuccess(s)
|
||||
}
|
||||
err = p.Run()
|
||||
if err != nil {
|
||||
recordFailure(s, fmt.Sprintf("Issue running ICMP to service %v, %v", s.Domain, err))
|
||||
return s
|
||||
}
|
||||
s.LastResponse = ""
|
||||
return s
|
||||
}
|
||||
|
||||
// checkTcp will check a TCP service
|
||||
func (s *Service) checkTcp(record bool) *Service {
|
||||
dnsLookup, err := s.dnsCheck()
|
||||
|
@ -123,17 +161,20 @@ func (s *Service) checkTcp(record bool) *Service {
|
|||
domain := fmt.Sprintf("%v", s.Domain)
|
||||
if s.Port != 0 {
|
||||
domain = fmt.Sprintf("%v:%v", s.Domain, s.Port)
|
||||
if isIPv6(s.Domain) {
|
||||
domain = fmt.Sprintf("[%v]:%v", s.Domain, s.Port)
|
||||
}
|
||||
}
|
||||
conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("%v Dial Error %v", s.Type, err))
|
||||
recordFailure(s, fmt.Sprintf("Dial Error %v", err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
if err := conn.Close(); err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("TCP Socket Close Error %v", err))
|
||||
recordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -161,10 +202,18 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
timeout := time.Duration(s.Timeout) * time.Second
|
||||
var content []byte
|
||||
var res *http.Response
|
||||
if s.Method == "POST" {
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, "application/json", nil, bytes.NewBuffer([]byte(s.PostData.String)), timeout)
|
||||
|
||||
var headers []string
|
||||
if s.Headers.Valid {
|
||||
headers = strings.Split(s.Headers.String, ",")
|
||||
} else {
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, nil, nil, nil, timeout)
|
||||
headers = nil
|
||||
}
|
||||
|
||||
if s.Method == "POST" {
|
||||
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, s.VerifySSL.Bool)
|
||||
}
|
||||
if err != nil {
|
||||
if record {
|
||||
|
@ -174,22 +223,13 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
}
|
||||
t2 := time.Now()
|
||||
s.Latency = t2.Sub(t1).Seconds()
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("HTTP Error %v", err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
s.LastResponse = string(content)
|
||||
s.LastStatusCode = res.StatusCode
|
||||
|
||||
if s.Expected.String != "" {
|
||||
if err != nil {
|
||||
utils.Log(2, err)
|
||||
}
|
||||
match, err := regexp.MatchString(s.Expected.String, string(content))
|
||||
if err != nil {
|
||||
utils.Log(2, err)
|
||||
utils.Log(2, fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String))
|
||||
}
|
||||
if !match {
|
||||
if record {
|
||||
|
@ -204,26 +244,14 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
}
|
||||
return s
|
||||
}
|
||||
s.Online = true
|
||||
if record {
|
||||
recordSuccess(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Check will run checkHttp for HTTP services and checkTcp for TCP services
|
||||
func (s *Service) Check(record bool) {
|
||||
switch s.Type {
|
||||
case "http":
|
||||
s.checkHttp(record)
|
||||
case "tcp", "udp":
|
||||
s.checkTcp(record)
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -234,11 +262,12 @@ func recordSuccess(s *Service) {
|
|||
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)
|
||||
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{
|
||||
Service: s.Id,
|
||||
Issue: issue,
|
||||
|
@ -248,5 +277,9 @@ func recordFailure(s *Service, issue string) {
|
|||
}}
|
||||
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
|
||||
s.CreateFailure(fail)
|
||||
s.Online = false
|
||||
s.SuccessNotified = false
|
||||
s.UpdateNotify = CoreApp.UpdateNotify.Bool
|
||||
s.DownText = s.DowntimeText()
|
||||
notifier.OnFailure(s.Service, fail.Failure)
|
||||
}
|
||||
|
|
|
@ -80,9 +80,18 @@ func LoadUsingEnv() (*DbConfig, error) {
|
|||
utils.Log(3, err)
|
||||
}
|
||||
|
||||
username := os.Getenv("ADMIN_USER")
|
||||
if username == "" {
|
||||
username = "admin"
|
||||
}
|
||||
password := os.Getenv("ADMIN_PASSWORD")
|
||||
if password == "" {
|
||||
password = "admin"
|
||||
}
|
||||
|
||||
admin := ReturnUser(&types.User{
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: "info@admin.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
|
|
|
@ -33,6 +33,7 @@ func init() {
|
|||
utils.InitLogs()
|
||||
source.Assets()
|
||||
skipNewDb = false
|
||||
SampleHits = 480
|
||||
}
|
||||
|
||||
func TestNewCore(t *testing.T) {
|
||||
|
|
|
@ -36,7 +36,7 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}}
|
||||
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}}
|
||||
}
|
||||
|
||||
// DbConfig stores the config.yml file for the statup configuration
|
||||
|
@ -87,12 +87,21 @@ func groupsDb() *gorm.DB {
|
|||
return DbSession.Model(&types.Group{})
|
||||
}
|
||||
|
||||
// incidentsDB returns the 'incidents' database column
|
||||
func incidentsDB() *gorm.DB {
|
||||
return DbSession.Model(&types.Incident{})
|
||||
}
|
||||
|
||||
// incidentsUpdatesDB returns the 'incidents updates' database column
|
||||
func incidentsUpdatesDB() *gorm.DB {
|
||||
return DbSession.Model(&types.IncidentUpdate{})
|
||||
}
|
||||
|
||||
// HitsBetween returns the gorm database query for a collection of service hits between a time range
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) *gorm.DB {
|
||||
selector := Dbtimestamp(group, column)
|
||||
if CoreApp.DbConnection == "postgres" {
|
||||
timeQuery := fmt.Sprintf("service = %v AND created_at BETWEEN '%v.000000' AND '%v.000000'", s.Id, t1.UTC().Format(types.POSTGRES_TIME), t2.UTC().Format(types.POSTGRES_TIME))
|
||||
return hitsDB().Select(selector).Where(timeQuery)
|
||||
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))
|
||||
}
|
||||
|
@ -194,7 +203,7 @@ func (db *DbConfig) Connect(retry bool, location string) error {
|
|||
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", Configs.DbUser, Configs.DbPass, host, Configs.DbData)
|
||||
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)
|
||||
case "postgres":
|
||||
sslMode := "disable"
|
||||
if postgresSSL != "" {
|
||||
|
@ -208,16 +217,19 @@ func (db *DbConfig) Connect(retry bool, location string) error {
|
|||
dbSession, err := gorm.Open(dbType, conn)
|
||||
if err != nil {
|
||||
if retry {
|
||||
utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", conn))
|
||||
utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", Configs.DbHost))
|
||||
return db.waitForDb()
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if dbType == "sqlite3" {
|
||||
dbSession.DB().SetMaxOpenConns(1)
|
||||
}
|
||||
err = dbSession.DB().Ping()
|
||||
if err == 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))
|
||||
utils.Log(1, fmt.Sprintf("Database %v connection was successful.", dbType))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -320,6 +332,8 @@ func (db *DbConfig) DropDatabase() error {
|
|||
err = DbSession.DropTableIfExists("services")
|
||||
err = DbSession.DropTableIfExists("users")
|
||||
err = DbSession.DropTableIfExists("messages")
|
||||
err = DbSession.DropTableIfExists("incidents")
|
||||
err = DbSession.DropTableIfExists("incident_updates")
|
||||
return err.Error
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ func CountFailures() uint64 {
|
|||
func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
date := ago.UTC().Format("2006-01-02 00:00:00")
|
||||
dateend := ago.UTC().Format("2006-01-02 23:59:59")
|
||||
dateend := ago.UTC().Format("2006-01-02") + " 23:59:59"
|
||||
rows := failuresDB().Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
|
|
|
@ -45,15 +45,26 @@ func (g *Group) Services() []*Service {
|
|||
return services
|
||||
}
|
||||
|
||||
// VisibleServices returns all services based on authentication
|
||||
func (g *Group) VisibleServices(auth bool) []*Service {
|
||||
var services []*Service
|
||||
for _, g := range g.Services() {
|
||||
if !g.Public.Bool {
|
||||
if auth {
|
||||
services = append(services, g)
|
||||
}
|
||||
} else {
|
||||
services = append(services, g)
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
// SelectGroups returns all groups
|
||||
func SelectGroups(includeAll bool, auth bool) []*Group {
|
||||
var groups []*Group
|
||||
var validGroups []*Group
|
||||
groupsDb().Find(&groups).Order("order_id desc")
|
||||
if includeAll {
|
||||
emptyGroup := &Group{&types.Group{Id: 0, Public: types.NewNullBool(true)}}
|
||||
groups = append(groups, emptyGroup)
|
||||
}
|
||||
for _, g := range groups {
|
||||
if !g.Public.Bool {
|
||||
if auth {
|
||||
|
@ -64,6 +75,10 @@ func SelectGroups(includeAll bool, auth bool) []*Group {
|
|||
}
|
||||
}
|
||||
sort.Sort(GroupOrder(validGroups))
|
||||
if includeAll {
|
||||
emptyGroup := &Group{&types.Group{Id: 0, Public: types.NewNullBool(true)}}
|
||||
validGroups = append(validGroups, emptyGroup)
|
||||
}
|
||||
return validGroups
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Incident struct {
|
||||
*types.Incident
|
||||
}
|
||||
|
||||
type IncidentUpdate struct {
|
||||
*types.IncidentUpdate
|
||||
}
|
||||
|
||||
// AllIncidents will return all incidents and updates recorded
|
||||
func AllIncidents() []*Incident {
|
||||
var incidents []*Incident
|
||||
incidentsDB().Find(&incidents).Order("id desc")
|
||||
for _, i := range incidents {
|
||||
var updates []*types.IncidentUpdate
|
||||
incidentsUpdatesDB().Find(&updates).Order("id desc")
|
||||
i.Updates = updates
|
||||
}
|
||||
return incidents
|
||||
}
|
||||
|
||||
// Incidents will return the all incidents for a service
|
||||
func (s *Service) Incidents() []*Incident {
|
||||
var incidentArr []*Incident
|
||||
incidentsDB().Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
|
||||
return incidentArr
|
||||
}
|
||||
|
||||
// AllUpdates will return the all updates for an incident
|
||||
func (i *Incident) AllUpdates() []*IncidentUpdate {
|
||||
var updatesArr []*IncidentUpdate
|
||||
incidentsUpdatesDB().Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
|
||||
return updatesArr
|
||||
}
|
||||
|
||||
// Delete will remove a incident
|
||||
func (i *Incident) Delete() error {
|
||||
err := incidentsDB().Delete(i)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Create will create a incident and insert it into the database
|
||||
func (i *Incident) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now()
|
||||
db := incidentsDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
||||
// Update will update a incident
|
||||
func (i *Incident) Update() (int64, error) {
|
||||
i.UpdatedAt = time.Now()
|
||||
db := incidentsDB().Update(i)
|
||||
return i.Id, db.Error
|
||||
}
|
||||
|
||||
// Delete will remove a incident update
|
||||
func (i *IncidentUpdate) Delete() error {
|
||||
err := incidentsUpdatesDB().Delete(i)
|
||||
return err.Error
|
||||
}
|
||||
|
||||
// Create will create a incident update and insert it into the database
|
||||
func (i *IncidentUpdate) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now()
|
||||
db := incidentsUpdatesDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
}
|
|
@ -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,23 @@ 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()
|
||||
utils.Log(1, fmt.Sprintf("Sending failure %v notification for service %v", notifier.Method, s.Name))
|
||||
comm.(BasicEvents).OnFailure(s, f)
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +65,16 @@ 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()
|
||||
utils.Log(1, fmt.Sprintf("Sending successful %v notification for service %v", notifier.Method, s.Name))
|
||||
comm.(BasicEvents).OnSuccess(s)
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +84,7 @@ func OnSuccess(s *types.Service) {
|
|||
func OnNewService(s *types.Service) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending new service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnNewService(s)
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +97,7 @@ func OnUpdatedService(s *types.Service) {
|
|||
}
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending updated service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnUpdatedService(s)
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +110,7 @@ func OnDeletedService(s *types.Service) {
|
|||
}
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
|
||||
comm.(ServiceEvents).OnDeletedService(s)
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +120,7 @@ func OnDeletedService(s *types.Service) {
|
|||
func OnNewUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending new user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnNewUser(u)
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +130,7 @@ func OnNewUser(u *types.User) {
|
|||
func OnUpdatedUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending updated user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnUpdatedUser(u)
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +140,7 @@ func OnUpdatedUser(u *types.User) {
|
|||
func OnDeletedUser(u *types.User) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
|
||||
comm.(UserEvents).OnDeletedUser(u)
|
||||
}
|
||||
}
|
||||
|
@ -117,6 +150,7 @@ func OnDeletedUser(u *types.User) {
|
|||
func OnUpdatedCore(c *types.Core) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending updated core notification"))
|
||||
comm.(CoreEvents).OnUpdatedCore(c)
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +178,7 @@ func OnNewNotifier(n *Notification) {
|
|||
func OnUpdatedNotifier(n *Notification) {
|
||||
for _, comm := range AllCommunications {
|
||||
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
|
||||
utils.Log(1, fmt.Sprintf("Sending updated notifier for %v", n.Id))
|
||||
comm.(NotifierEvents).OnUpdatedNotifier(n)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,20 +101,20 @@ func (n *ExampleNotifier) Select() *Notification {
|
|||
// OnSave is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnSave() error {
|
||||
msg := fmt.Sprintf("received on save trigger")
|
||||
n.AddQueue(0, msg)
|
||||
n.AddQueue("onsave", msg)
|
||||
return errors.New("onsave triggered")
|
||||
}
|
||||
|
||||
// OnSuccess is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnSuccess(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a count trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnFailure is a required basic event for the Notifier interface
|
||||
func (n *ExampleNotifier) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf("received a failure trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnTest is a option testing event for the Notifier interface
|
||||
|
@ -126,61 +126,61 @@ func (n *ExampleNotifier) OnTest() error {
|
|||
// OnNewService is a option event for new services
|
||||
func (n *ExampleNotifier) OnNewService(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a new service trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedService is a option event for updated services
|
||||
func (n *ExampleNotifier) OnUpdatedService(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a update service trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnDeletedService is a option event for deleted services
|
||||
func (n *ExampleNotifier) OnDeletedService(s *types.Service) {
|
||||
msg := fmt.Sprintf("received a delete service trigger for service: %v\n", s.Name)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnNewUser is a option event for new users
|
||||
func (n *ExampleNotifier) OnNewUser(s *types.User) {
|
||||
msg := fmt.Sprintf("received a new user trigger for user: %v\n", s.Username)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedUser is a option event for updated users
|
||||
func (n *ExampleNotifier) OnUpdatedUser(s *types.User) {
|
||||
msg := fmt.Sprintf("received a updated user trigger for user: %v\n", s.Username)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnDeletedUser is a option event for deleted users
|
||||
func (n *ExampleNotifier) OnDeletedUser(s *types.User) {
|
||||
msg := fmt.Sprintf("received a deleted user trigger for user: %v\n", s.Username)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedCore is a option event when the settings are updated
|
||||
func (n *ExampleNotifier) OnUpdatedCore(s *types.Core) {
|
||||
msg := fmt.Sprintf("received a updated core trigger for core: %v\n", s.Name)
|
||||
n.AddQueue(0, msg)
|
||||
n.AddQueue("core", msg)
|
||||
}
|
||||
|
||||
// OnStart is triggered when statup has been started
|
||||
func (n *ExampleNotifier) OnStart(s *types.Core) {
|
||||
msg := fmt.Sprintf("received a trigger on Statping boot: %v\n", s.Name)
|
||||
n.AddQueue(0, msg)
|
||||
n.AddQueue(fmt.Sprintf("core"), msg)
|
||||
}
|
||||
|
||||
// OnNewNotifier is triggered when a new notifier has initialized
|
||||
func (n *ExampleNotifier) OnNewNotifier(s *Notification) {
|
||||
msg := fmt.Sprintf("received a new notifier trigger for notifier: %v\n", s.Method)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("notifier_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnUpdatedNotifier is triggered when a notifier has been updated
|
||||
func (n *ExampleNotifier) OnUpdatedNotifier(s *Notification) {
|
||||
msg := fmt.Sprintf("received a update notifier trigger for notifier: %v\n", s.Method)
|
||||
n.AddQueue(s.Id, msg)
|
||||
n.AddQueue(fmt.Sprintf("notifier_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// Create a new notifier that includes a form for the end user to insert their own values
|
||||
|
@ -224,7 +224,7 @@ func ExampleAddNotifier() {
|
|||
// OnSuccess will be triggered everytime a service is online
|
||||
func ExampleNotification_OnSuccess() {
|
||||
msg := fmt.Sprintf("this is a successful message as a string passing into AddQueue function")
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue("example", msg)
|
||||
fmt.Println(len(example.Queue))
|
||||
// Output: 1
|
||||
}
|
||||
|
@ -232,13 +232,13 @@ func ExampleNotification_OnSuccess() {
|
|||
// Add a new message into the queue OnSuccess
|
||||
func ExampleOnSuccess() {
|
||||
msg := fmt.Sprintf("received a count trigger for service: %v\n", service.Name)
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue("example", msg)
|
||||
}
|
||||
|
||||
// Add a new message into the queue OnFailure
|
||||
func ExampleOnFailure() {
|
||||
msg := fmt.Sprintf("received a failing service: %v\n", service.Name)
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue("example", msg)
|
||||
}
|
||||
|
||||
// OnTest allows your notifier to be testable
|
||||
|
@ -258,7 +258,7 @@ func ExampleNotification_CanTest() {
|
|||
// Add any type of interface to the AddQueue function to be ran in the queue
|
||||
func ExampleNotification_AddQueue() {
|
||||
msg := fmt.Sprintf("this is a failing message as a string passing into AddQueue function")
|
||||
example.AddQueue(0, msg)
|
||||
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
|
||||
|
|
|
@ -62,13 +62,12 @@ 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"`
|
||||
}
|
||||
|
||||
// QueueData is the struct for the messaging queue with service
|
||||
type QueueData struct {
|
||||
Id int64
|
||||
Id string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
|
@ -100,7 +99,7 @@ func (n *Notification) AfterFind() (err error) {
|
|||
}
|
||||
|
||||
// AddQueue will add any type of interface (json, string, struct, etc) into the Notifiers queue
|
||||
func (n *Notification) AddQueue(uid int64, msg interface{}) {
|
||||
func (n *Notification) AddQueue(uid string, msg interface{}) {
|
||||
data := &QueueData{uid, msg}
|
||||
n.Queue = append(n.Queue, data)
|
||||
}
|
||||
|
@ -180,6 +179,7 @@ func (n *Notification) makeLog(msg interface{}) {
|
|||
Time: utils.Timestamp(time.Now()),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Notifier %v has sent a message %v", n.Method, log.Message))
|
||||
n.logs = append(n.logs, log)
|
||||
}
|
||||
|
||||
|
@ -431,10 +431,10 @@ func (n *Notification) ResetQueue() {
|
|||
}
|
||||
|
||||
// ResetQueue will clear the notifiers Queue for a service
|
||||
func (n *Notification) ResetUniqueQueue(id int64) []*QueueData {
|
||||
func (n *Notification) ResetUniqueQueue(uid string) []*QueueData {
|
||||
var queue []*QueueData
|
||||
for _, v := range n.Queue {
|
||||
if v.Id != id {
|
||||
if v.Id != uid {
|
||||
queue = append(queue, v)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
|
@ -98,15 +99,15 @@ func TestSelectNotification(t *testing.T) {
|
|||
|
||||
func TestAddQueue(t *testing.T) {
|
||||
msg := "this is a test in the queue!"
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 1, len(example.Queue))
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 2, len(example.Queue))
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 3, len(example.Queue))
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 4, len(example.Queue))
|
||||
example.AddQueue(0, msg)
|
||||
example.AddQueue(fmt.Sprintf("service_%v", 0), msg)
|
||||
assert.Equal(t, 5, len(example.Queue))
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,14 @@ import (
|
|||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sampleStart = time.Now().Add((-24 * 7) * time.Hour).UTC()
|
||||
SampleHits = 9900.
|
||||
)
|
||||
|
||||
// InsertSampleData will create the example/dummy services for a brand new Statping installation
|
||||
func InsertSampleData() error {
|
||||
utils.Log(1, "Inserting Sample Data...")
|
||||
|
@ -103,11 +107,45 @@ func InsertSampleData() error {
|
|||
|
||||
insertMessages()
|
||||
|
||||
insertSampleIncidents()
|
||||
|
||||
utils.Log(1, "Sample data has finished importing")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertSampleIncidents() error {
|
||||
incident1 := &Incident{&types.Incident{
|
||||
Title: "Github Downtime",
|
||||
Description: "This is an example of a incident for a service.",
|
||||
ServiceId: 2,
|
||||
}}
|
||||
_, err := incident1.Create()
|
||||
|
||||
incidentUpdate1 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github's page for Statping seems to be sending a 501 error.",
|
||||
Type: "Investigating",
|
||||
}}
|
||||
_, err = incidentUpdate1.Create()
|
||||
|
||||
incidentUpdate2 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Problem is continuing and we are looking at the issues.",
|
||||
Type: "Update",
|
||||
}}
|
||||
_, err = incidentUpdate2.Create()
|
||||
|
||||
incidentUpdate3 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
}}
|
||||
_, err = incidentUpdate3.Create()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func insertSampleGroups() error {
|
||||
group1 := &Group{&types.Group{
|
||||
Name: "Main Services",
|
||||
|
@ -163,26 +201,31 @@ func insertSampleCheckins() error {
|
|||
|
||||
// InsertSampleHits will create a couple new hits for the sample services
|
||||
func InsertSampleHits() error {
|
||||
since := time.Now().Add((-24 * 7) * time.Hour).UTC()
|
||||
for i := int64(1); i <= 5; i++ {
|
||||
service := SelectService(i)
|
||||
utils.Log(1, fmt.Sprintf("Adding %v sample hit records to service %v", 360, service.Name))
|
||||
createdAt := since
|
||||
alpha := float64(1.05)
|
||||
|
||||
for hi := int64(1); hi <= 168; hi++ {
|
||||
alpha += 0.01
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
latency := rand.Float64() * alpha
|
||||
createdAt = createdAt.Add(1 * time.Hour)
|
||||
for i := int64(1); i <= 5; i++ {
|
||||
|
||||
service := SelectService(i)
|
||||
seed := time.Now().UnixNano()
|
||||
|
||||
utils.Log(1, 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,
|
||||
}
|
||||
service.CreateHit(hit)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -397,7 +440,7 @@ func InsertLargeSampleData() error {
|
|||
|
||||
var dayAgo = time.Now().Add((-24 * 90) * time.Hour)
|
||||
|
||||
insertHitRecords(dayAgo, 1450)
|
||||
insertHitRecords(dayAgo, 5450)
|
||||
|
||||
insertFailureRecords(dayAgo, 730)
|
||||
|
||||
|
@ -431,10 +474,9 @@ func insertHitRecords(since time.Time, amount int64) {
|
|||
service := SelectService(i)
|
||||
utils.Log(1, 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++ {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
latency := rand.Float64()
|
||||
latency := p.Noise1D(float64(hi / 10))
|
||||
createdAt = createdAt.Add(1 * time.Minute)
|
||||
hit := &types.Hit{
|
||||
Service: service.Id,
|
||||
|
|
|
@ -55,6 +55,21 @@ func SelectService(id int64) *Service {
|
|||
return nil
|
||||
}
|
||||
|
||||
func SelectServices(auth bool) []*Service {
|
||||
var validServices []*Service
|
||||
for _, sr := range CoreApp.Services {
|
||||
s := sr.(*Service)
|
||||
if !s.Public.Bool {
|
||||
if auth {
|
||||
validServices = append(validServices, s)
|
||||
}
|
||||
} else {
|
||||
validServices = append(validServices, s)
|
||||
}
|
||||
}
|
||||
return validServices
|
||||
}
|
||||
|
||||
// SelectServiceLink returns a *core.Service from the service permalink
|
||||
func SelectServiceLink(permalink string) *Service {
|
||||
for _, s := range Services() {
|
||||
|
@ -181,13 +196,13 @@ func (s *Service) lastFailure() *Failure {
|
|||
// // Online since Monday 3:04:05PM, Jan _2 2006
|
||||
func (s *Service) SmallText() string {
|
||||
last := s.LimitedFailures(1)
|
||||
hits, _ := s.LimitedHits(1)
|
||||
//hits, _ := s.LimitedHits(1)
|
||||
zone := CoreApp.Timezone
|
||||
if s.Online {
|
||||
if len(last) == 0 {
|
||||
return fmt.Sprintf("Online since %v", utils.Timezoner(s.CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
} else {
|
||||
return fmt.Sprintf("Online, last Failure was %v", utils.Timezoner(hits[0].CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
return fmt.Sprintf("Online, last Failure was %v", utils.Timezoner(last[0].CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
}
|
||||
}
|
||||
if len(last) > 0 {
|
||||
|
@ -378,7 +393,7 @@ func (s *Service) Update(restart bool) error {
|
|||
if !s.AllowNotifications.Bool {
|
||||
for _, n := range CoreApp.Notifications {
|
||||
notif := n.(notifier.Notifier).Select()
|
||||
notif.ResetUniqueQueue(s.Id)
|
||||
notif.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
}
|
||||
}
|
||||
if restart {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -382,7 +379,7 @@ func TestSelectGroups(t *testing.T) {
|
|||
groups := SelectGroups(true, false)
|
||||
assert.Equal(t, int(3), len(groups))
|
||||
groups = SelectGroups(true, true)
|
||||
assert.Equal(t, int(4), len(groups))
|
||||
assert.Equal(t, int(5), len(groups))
|
||||
}
|
||||
|
||||
func TestService_TotalFailures(t *testing.T) {
|
||||
|
@ -400,14 +397,15 @@ func TestService_TotalFailures24(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestService_TotalFailuresOnDate(t *testing.T) {
|
||||
t.SkipNow()
|
||||
ago := time.Now().UTC()
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailuresOnDate(ago)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(0), failures)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestCountFailures(t *testing.T) {
|
||||
failures := CountFailures()
|
||||
assert.Equal(t, uint64(1463), failures)
|
||||
assert.NotEqual(t, uint64(0), failures)
|
||||
}
|
||||
|
|
|
@ -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, err)
|
||||
utils.Log(2, fmt.Errorf("user %v not found", username))
|
||||
return nil, false
|
||||
}
|
||||
if CheckHash(password, user.Password) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
FROM cypress/browsers:chrome67
|
||||
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
# Statping 'test' image for running a full test using the production environment
|
||||
|
||||
WORKDIR $HOME/statping
|
||||
|
@ -12,4 +12,4 @@ RUN npm install
|
|||
ADD ./statping-linux-amd64 /usr/local/bin/statping
|
||||
RUN statping version
|
||||
|
||||
RUN npm run test-docker
|
||||
RUN npm run test-docker
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
FROM alpine
|
||||
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
|
||||
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
|
||||
|
||||
ENV STATPING_VERSION=0.80.35
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
|
@ -36,21 +35,12 @@ type apiResponse struct {
|
|||
}
|
||||
|
||||
func apiIndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
coreClone := *core.CoreApp
|
||||
coreClone.Started = utils.Timezoner(core.CoreApp.Started, core.CoreApp.Timezone)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(coreClone)
|
||||
returnJson(coreClone, w, r)
|
||||
}
|
||||
|
||||
func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var err error
|
||||
core.CoreApp.ApiKey = utils.NewSHA1Hash(40)
|
||||
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
|
||||
|
@ -63,10 +53,6 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
CacheStorage = NewStorage()
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
@ -77,8 +63,7 @@ func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
|
|||
Status: "error",
|
||||
Error: err.Error(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
returnJson(output, w, r)
|
||||
}
|
||||
|
||||
func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -120,6 +105,12 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
|
|||
case *types.Checkin:
|
||||
objName = "checkin"
|
||||
objId = v.Id
|
||||
case *core.Incident:
|
||||
objName = "incident"
|
||||
objId = v.Id
|
||||
case *core.IncidentUpdate:
|
||||
objName = "incident_update"
|
||||
objId = v.Id
|
||||
default:
|
||||
objName = "missing"
|
||||
}
|
||||
|
@ -132,8 +123,7 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
|
|||
Output: obj,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
returnJson(output, w, r)
|
||||
}
|
||||
|
||||
func sendUnauthorizedJson(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -141,7 +131,6 @@ 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)
|
||||
json.NewEncoder(w).Encode(output)
|
||||
returnJson(output, w, r)
|
||||
}
|
||||
|
|
|
@ -102,6 +102,12 @@ func TestMainApiRoutes(t *testing.T) {
|
|||
URL: "/api/clear_cache",
|
||||
Method: "POST",
|
||||
ExpectedStatus: 303,
|
||||
},
|
||||
{
|
||||
Name: "404 Error Page",
|
||||
URL: "/api/missing_404_page",
|
||||
Method: "GET",
|
||||
ExpectedStatus: 404,
|
||||
}}
|
||||
|
||||
for _, v := range tests {
|
||||
|
@ -169,7 +175,7 @@ func TestApiServiceRoutes(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Name: "Statping Reorder Services",
|
||||
URL: "/api/reorder",
|
||||
URL: "/api/services/reorder",
|
||||
Method: "POST",
|
||||
Body: `[{"service":1,"order":1},{"service":5,"order":2},{"service":2,"order":3},{"service":3,"order":4},{"service":4,"order":5}]`,
|
||||
ExpectedStatus: 200,
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/core"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
@ -74,31 +71,3 @@ func (s Storage) Set(key string, content []byte, duration time.Duration) {
|
|||
Expiration: time.Now().Add(duration).UnixNano(),
|
||||
}
|
||||
}
|
||||
|
||||
func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
content := CacheStorage.Get(r.RequestURI)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
if core.Configs == nil {
|
||||
handler(w, r)
|
||||
return
|
||||
}
|
||||
if content != nil {
|
||||
w.Write(content)
|
||||
} else {
|
||||
c := httptest.NewRecorder()
|
||||
handler(c, r)
|
||||
content := c.Body.Bytes()
|
||||
result := c.Result()
|
||||
if result.StatusCode != 200 {
|
||||
w.WriteHeader(result.StatusCode)
|
||||
w.Write(content)
|
||||
return
|
||||
}
|
||||
w.Write(content)
|
||||
if d, err := time.ParseDuration(duration); err == nil {
|
||||
go CacheStorage.Set(r.RequestURI, content, d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -28,24 +28,15 @@ import (
|
|||
)
|
||||
|
||||
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
checkins := core.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Hits = c.AllHits()
|
||||
c.Failures = c.LimitedFailures(64)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(checkins)
|
||||
returnJson(checkins, w, r)
|
||||
}
|
||||
|
||||
func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
checkin := core.SelectCheckin(vars["api"])
|
||||
if checkin == nil {
|
||||
|
@ -54,15 +45,10 @@ func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
checkin.Hits = checkin.LimitedHits(32)
|
||||
checkin.Failures = checkin.LimitedFailures(32)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(checkin)
|
||||
returnJson(checkin, w, r)
|
||||
}
|
||||
|
||||
func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var checkin *core.Checkin
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&checkin)
|
||||
|
@ -110,15 +96,10 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
checkin.Failing = false
|
||||
checkin.LastHit = utils.Timezoner(time.Now().UTC(), core.CoreApp.Timezone)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
sendJsonAction(checkinHit, "update", w, r)
|
||||
}
|
||||
|
||||
func checkinDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
checkin := core.SelectCheckin(vars["api"])
|
||||
if checkin == nil {
|
||||
|
|
|
@ -41,9 +41,9 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|||
resetCookies()
|
||||
}
|
||||
session, _ := sessionStore.Get(r, cookieKey)
|
||||
r.ParseForm()
|
||||
username := r.PostForm.Get("username")
|
||||
password := r.PostForm.Get("password")
|
||||
form := parseForm(r)
|
||||
username := form.Get("username")
|
||||
password := form.Get("password")
|
||||
user, auth := core.AuthUser(username, password)
|
||||
if auth {
|
||||
session.Values["authenticated"] = true
|
||||
|
@ -77,10 +77,6 @@ func helpHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func logsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
utils.LockLines.Lock()
|
||||
logs := make([]string, 0)
|
||||
length := len(utils.LastLines)
|
||||
|
@ -93,10 +89,6 @@ func logsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func logsLineHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if lastLine := utils.GetLastLine(); lastLine != nil {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -105,11 +97,6 @@ func logsLineHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func exportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var notifiers []*notifier.Notification
|
||||
for _, v := range core.CoreApp.Notifications {
|
||||
notifier := v.(notifier.Notifier)
|
||||
|
|
|
@ -38,10 +38,21 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
|||
"Services": func() []types.ServiceInterface {
|
||||
return core.CoreApp.Services
|
||||
},
|
||||
"VisibleServices": func() []*core.Service {
|
||||
auth := IsUser(r)
|
||||
return core.SelectServices(auth)
|
||||
},
|
||||
"VisibleGroupServices": func(group *core.Group) []*core.Service {
|
||||
auth := IsUser(r)
|
||||
return group.VisibleServices(auth)
|
||||
},
|
||||
"Groups": func(includeAll bool) []*core.Group {
|
||||
auth := IsUser(r)
|
||||
return core.SelectGroups(includeAll, auth)
|
||||
},
|
||||
"Group": func(id int) *core.Group {
|
||||
return core.SelectGroup(int64(id))
|
||||
},
|
||||
"len": func(g interface{}) int {
|
||||
val := reflect.ValueOf(g)
|
||||
return val.Len()
|
||||
|
@ -52,6 +63,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)
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
|||
# .gqlgen.yml example
|
||||
#
|
||||
# Refer to https://gqlgen.com/config/
|
||||
# for detailed .gqlgen.yml documentation.
|
||||
|
||||
schema:
|
||||
- schema.graphql
|
||||
exec:
|
||||
filename: generated.go
|
||||
model:
|
||||
filename: models_gen.go
|
||||
models:
|
||||
Core:
|
||||
model: github.com/hunterlong/statping/types.Core
|
||||
Message:
|
||||
model: github.com/hunterlong/statping/types.Message
|
||||
Group:
|
||||
model: github.com/hunterlong/statping/types.Group
|
||||
Service:
|
||||
model: github.com/hunterlong/statping/types.Service
|
||||
User:
|
||||
model: github.com/hunterlong/statping/types.User
|
||||
Failure:
|
||||
model: github.com/hunterlong/statping/types.Failure
|
||||
Checkin:
|
||||
model: github.com/hunterlong/statping/types.Checkin
|
||||
CheckinHit:
|
||||
model: github.com/hunterlong/statping/types.CheckinHit
|
||||
ID:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Int64
|
||||
struct_tag: json
|
||||
resolver:
|
||||
filename: resolver.go
|
||||
type: Resolver
|
|
@ -0,0 +1,188 @@
|
|||
//go:generate go run github.com/99designs/gqlgen
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/hunterlong/statping/core"
|
||||
|
||||
"github.com/hunterlong/statping/types"
|
||||
)
|
||||
|
||||
// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
|
||||
|
||||
type Resolver struct{}
|
||||
|
||||
func (r *Resolver) Checkin() CheckinResolver {
|
||||
return &checkinResolver{r}
|
||||
}
|
||||
func (r *Resolver) Core() CoreResolver {
|
||||
return &coreResolver{r}
|
||||
}
|
||||
func (r *Resolver) Group() GroupResolver {
|
||||
return &groupResolver{r}
|
||||
}
|
||||
func (r *Resolver) Message() MessageResolver {
|
||||
return &messageResolver{r}
|
||||
}
|
||||
func (r *Resolver) Query() QueryResolver {
|
||||
return &queryResolver{r}
|
||||
}
|
||||
func (r *Resolver) Service() ServiceResolver {
|
||||
return &serviceResolver{r}
|
||||
}
|
||||
func (r *Resolver) User() UserResolver {
|
||||
return &userResolver{r}
|
||||
}
|
||||
|
||||
type checkinResolver struct{ *Resolver }
|
||||
|
||||
func (r *checkinResolver) Service(ctx context.Context, obj *types.Checkin) (*types.Service, error) {
|
||||
service := core.SelectService(obj.ServiceId)
|
||||
return service.Service, nil
|
||||
}
|
||||
func (r *checkinResolver) Failures(ctx context.Context, obj *types.Checkin) ([]*types.Failure, error) {
|
||||
all := obj.Failures
|
||||
var objs []*types.Failure
|
||||
for _, v := range all {
|
||||
objs = append(objs, v.Select())
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
type coreResolver struct{ *Resolver }
|
||||
|
||||
func (r *coreResolver) Footer(ctx context.Context, obj *types.Core) (string, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *coreResolver) Timezone(ctx context.Context, obj *types.Core) (string, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *coreResolver) UsingCdn(ctx context.Context, obj *types.Core) (bool, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type messageResolver struct{ *Resolver }
|
||||
|
||||
func (r *messageResolver) NotifyUsers(ctx context.Context, obj *types.Message) (bool, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *messageResolver) NotifyMethod(ctx context.Context, obj *types.Message) (bool, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *messageResolver) NotifyBefore(ctx context.Context, obj *types.Message) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type groupResolver struct{ *Resolver }
|
||||
|
||||
func (r *groupResolver) Public(ctx context.Context, obj *types.Group) (bool, error) {
|
||||
return obj.Public.Bool, nil
|
||||
}
|
||||
|
||||
type queryResolver struct{ *Resolver }
|
||||
|
||||
func (r *queryResolver) Core(ctx context.Context) (*types.Core, error) {
|
||||
c := core.CoreApp
|
||||
return c.Core, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Message(ctx context.Context, id int64) (*types.Message, error) {
|
||||
message, err := core.SelectMessage(id)
|
||||
return message.Message, err
|
||||
}
|
||||
func (r *queryResolver) Messages(ctx context.Context) ([]*types.Message, error) {
|
||||
all, err := core.SelectMessages()
|
||||
var objs []*types.Message
|
||||
for _, v := range all {
|
||||
objs = append(objs, v.Message)
|
||||
}
|
||||
return objs, err
|
||||
}
|
||||
|
||||
func (r *queryResolver) Group(ctx context.Context, id int64) (*types.Group, error) {
|
||||
group := core.SelectGroup(id)
|
||||
return group.Group, nil
|
||||
}
|
||||
func (r *queryResolver) Groups(ctx context.Context) ([]*types.Group, error) {
|
||||
all := core.SelectGroups(true, true)
|
||||
var objs []*types.Group
|
||||
for _, v := range all {
|
||||
objs = append(objs, v.Group)
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Checkin(ctx context.Context, id int64) (*types.Checkin, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (r *queryResolver) Checkins(ctx context.Context) ([]*types.Checkin, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (r *queryResolver) User(ctx context.Context, id int64) (*types.User, error) {
|
||||
user, err := core.SelectUser(id)
|
||||
return user.User, err
|
||||
}
|
||||
func (r *queryResolver) Users(ctx context.Context) ([]*types.User, error) {
|
||||
all, err := core.SelectAllUsers()
|
||||
var objs []*types.User
|
||||
for _, v := range all {
|
||||
objs = append(objs, v.User)
|
||||
}
|
||||
return objs, err
|
||||
}
|
||||
|
||||
type userResolver struct{ *Resolver }
|
||||
|
||||
func (r *userResolver) Admin(ctx context.Context, obj *types.User) (bool, error) {
|
||||
return obj.Admin.Bool, nil
|
||||
}
|
||||
|
||||
type serviceResolver struct{ *Resolver }
|
||||
|
||||
func (r *queryResolver) Service(ctx context.Context, id int64) (*types.Service, error) {
|
||||
service := core.SelectService(id)
|
||||
return service.Service, nil
|
||||
}
|
||||
func (r *queryResolver) Services(ctx context.Context) ([]*types.Service, error) {
|
||||
all := core.Services()
|
||||
var objs []*types.Service
|
||||
for _, v := range all {
|
||||
objs = append(objs, v.Select())
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func (r *serviceResolver) Expected(ctx context.Context, obj *types.Service) (string, error) {
|
||||
return obj.Expected.String, nil
|
||||
}
|
||||
func (r *serviceResolver) PostData(ctx context.Context, obj *types.Service) (string, error) {
|
||||
return obj.PostData.String, nil
|
||||
}
|
||||
func (r *serviceResolver) AllowNotifications(ctx context.Context, obj *types.Service) (bool, error) {
|
||||
return obj.AllowNotifications.Bool, nil
|
||||
}
|
||||
func (r *serviceResolver) Public(ctx context.Context, obj *types.Service) (bool, error) {
|
||||
return obj.Public.Bool, nil
|
||||
}
|
||||
func (r *serviceResolver) Headers(ctx context.Context, obj *types.Service) (string, error) {
|
||||
return obj.Headers.String, nil
|
||||
}
|
||||
func (r *serviceResolver) Permalink(ctx context.Context, obj *types.Service) (string, error) {
|
||||
return obj.Permalink.String, nil
|
||||
}
|
||||
func (r *serviceResolver) Online24Hours(ctx context.Context, obj *types.Service) (float64, error) {
|
||||
return float64(obj.Online24Hours), nil
|
||||
}
|
||||
func (r *serviceResolver) Failures(ctx context.Context, obj *types.Service) ([]*types.Failure, error) {
|
||||
all := obj.Failures
|
||||
var objs []*types.Failure
|
||||
for _, v := range all {
|
||||
objs = append(objs, v.Select())
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
func (r *serviceResolver) Group(ctx context.Context, obj *types.Service) (*types.Group, error) {
|
||||
group := core.SelectGroup(int64(obj.GroupId))
|
||||
return group.Group, nil
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
schema {
|
||||
query: Query
|
||||
}
|
||||
|
||||
type Query {
|
||||
core: Core
|
||||
service(id: ID!): Service
|
||||
services: [Service]!
|
||||
group(id: ID!): Group
|
||||
groups: [Group]!
|
||||
user(id: ID!): User
|
||||
users: [User]!
|
||||
checkin(id: ID!): Checkin
|
||||
checkins: [Checkin]!
|
||||
message(id: ID!): Message
|
||||
messages: [Message]!
|
||||
}
|
||||
|
||||
type Core {
|
||||
name: String!
|
||||
description: String!
|
||||
footer: String!
|
||||
domain: String!
|
||||
version: String!
|
||||
timezone: String!
|
||||
using_cdn: Boolean!
|
||||
started_on: Time!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
type Service {
|
||||
id: ID!
|
||||
name: String!
|
||||
domain: String!
|
||||
expected: String!
|
||||
expected_status: Int!
|
||||
interval: Int!
|
||||
type: String!
|
||||
method: String!
|
||||
post_data: String!
|
||||
port: Int!
|
||||
timeout: Int!
|
||||
order_id: Int!
|
||||
allow_notifications: Boolean!
|
||||
public: Boolean!
|
||||
group: Group!
|
||||
headers: String!
|
||||
permalink: String!
|
||||
online: Boolean!
|
||||
latency: Float!
|
||||
ping_time: Float!
|
||||
online_24_hours: Float!
|
||||
avg_response: String!
|
||||
status_code: Int!
|
||||
last_success: Time!
|
||||
failures: [Failure]
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
type Checkin {
|
||||
id: ID!
|
||||
service: Service!
|
||||
name: String!
|
||||
interval: Int!
|
||||
grace: Int!
|
||||
api_key: String!
|
||||
failing: Boolean!
|
||||
last_hit: Time!
|
||||
failures: [Failure]
|
||||
hits: [CheckinHit]
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
type CheckinHit {
|
||||
id: ID!
|
||||
from: String!
|
||||
created_at: Time!
|
||||
}
|
||||
|
||||
type Group {
|
||||
id: ID!
|
||||
name: String!
|
||||
public: Boolean!
|
||||
order_id: Int!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
username: String!
|
||||
email: String!
|
||||
api_key: String!
|
||||
api_secret: String!
|
||||
admin: Boolean!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
type Failure {
|
||||
id: ID!
|
||||
issue: String!
|
||||
method: String!
|
||||
method_id: Int!
|
||||
error_code: Int!
|
||||
ping: Float!
|
||||
created_at: Time!
|
||||
}
|
||||
|
||||
type Message {
|
||||
id: ID!
|
||||
title: String!
|
||||
description: String!
|
||||
start_on: Time!
|
||||
end_on: Time!
|
||||
notify_users: Boolean!
|
||||
notify_method: Boolean!
|
||||
notify_before: Int!
|
||||
notify_before_scale: String!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
scalar Time
|
|
@ -24,40 +24,59 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
// apiAllGroupHandler will show all the groups
|
||||
func apiAllGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
func groupViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
var group *core.Group
|
||||
id := vars["id"]
|
||||
group = core.SelectGroup(utils.ToInt(id))
|
||||
|
||||
if group == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
auth := IsUser(r)
|
||||
groups := core.SelectGroups(false, auth)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(groups)
|
||||
|
||||
ExecuteResponse(w, r, "group.gohtml", group, nil)
|
||||
}
|
||||
|
||||
// apiAllGroupHandler will show all the groups
|
||||
func apiAllGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
auth, admin := IsUser(r), IsAdmin(r)
|
||||
groups := core.SelectGroups(admin, auth)
|
||||
returnJson(groups, w, r)
|
||||
}
|
||||
|
||||
// apiGroupHandler will show a single group
|
||||
func apiGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
group := core.SelectGroup(utils.ToInt(vars["id"]))
|
||||
if group == nil {
|
||||
sendErrorJson(errors.New("group not found"), w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(group)
|
||||
returnJson(group, w, r)
|
||||
}
|
||||
|
||||
// apiGroupUpdateHandler will update a group
|
||||
func apiGroupUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
group := core.SelectGroup(utils.ToInt(vars["id"]))
|
||||
if group == nil {
|
||||
sendErrorJson(errors.New("group not found"), w, r)
|
||||
return
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.Decode(&group)
|
||||
_, err := group.Update()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
sendJsonAction(group, "update", w, r)
|
||||
}
|
||||
|
||||
// apiCreateGroupHandler accepts a POST method to create new groups
|
||||
func apiCreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var group *core.Group
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&group)
|
||||
|
@ -75,10 +94,6 @@ func apiCreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// apiGroupDeleteHandler accepts a DELETE method to delete groups
|
||||
func apiGroupDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
group := core.SelectGroup(utils.ToInt(vars["id"]))
|
||||
if group == nil {
|
||||
|
@ -99,10 +114,6 @@ type groupOrder struct {
|
|||
}
|
||||
|
||||
func apiGroupReorderHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
var newOrder []*groupOrder
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
@ -112,6 +123,5 @@ func apiGroupReorderHandler(w http.ResponseWriter, r *http.Request) {
|
|||
group.Order = g.Order
|
||||
group.Update()
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(newOrder)
|
||||
returnJson(newOrder, w, r)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/hunterlong/statping/core"
|
||||
|
@ -216,13 +217,11 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
|||
utils.Log(4, err)
|
||||
}
|
||||
// render the page requested
|
||||
_, err = mainTemplate.Parse(render)
|
||||
if err != nil {
|
||||
if _, err := mainTemplate.Parse(render); err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
// execute the template
|
||||
err = mainTemplate.Execute(w, data)
|
||||
if err != nil {
|
||||
if err := mainTemplate.Execute(w, data); err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
}
|
||||
|
@ -245,15 +244,17 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
|
|||
return core.CoreApp.Services
|
||||
},
|
||||
})
|
||||
_, err = t.Parse(render)
|
||||
if err != nil {
|
||||
if _, err := t.Parse(render); err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = t.Execute(w, data)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
func returnJson(d interface{}, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(d)
|
||||
}
|
||||
|
||||
// error404Handler is a HTTP handler for 404 error pages
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/core"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func apiAllIncidentsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
incidents := core.AllIncidents()
|
||||
returnJson(incidents, w, r)
|
||||
}
|
|
@ -16,7 +16,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -30,10 +29,10 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
health := map[string]interface{}{
|
||||
"services": len(core.Services()),
|
||||
"online": core.Configs != nil,
|
||||
"online": true,
|
||||
"setup": core.Configs != nil,
|
||||
}
|
||||
json.NewEncoder(w).Encode(health)
|
||||
returnJson(health, w, r)
|
||||
}
|
||||
|
|
|
@ -35,10 +35,6 @@ func messagesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func viewMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
id := utils.ToInt(vars["id"])
|
||||
message, err := core.SelectMessage(id)
|
||||
|
@ -50,24 +46,15 @@ func viewMessageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiAllMessagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
messages, err := core.SelectMessages()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(messages)
|
||||
returnJson(messages, w, r)
|
||||
}
|
||||
|
||||
func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var message *types.Message
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&message)
|
||||
|
@ -85,25 +72,16 @@ func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
message, err := core.SelectMessage(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(message)
|
||||
returnJson(message, w, r)
|
||||
}
|
||||
|
||||
func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
message, err := core.SelectMessage(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
|
@ -119,10 +97,6 @@ func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
message, err := core.SelectMessage(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/core"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
sendUnauthorizedJson(w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
handler(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// readOnly is a middleware function to check if user is a User before running original request
|
||||
func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
if redirect {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
} else {
|
||||
sendUnauthorizedJson(w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
handler(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// cached is a middleware function that accepts a duration and content type and will cache the response of the original request
|
||||
func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
content := CacheStorage.Get(r.RequestURI)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if core.Configs == nil {
|
||||
handler(w, r)
|
||||
return
|
||||
}
|
||||
if content != nil {
|
||||
w.Write(content)
|
||||
} else {
|
||||
c := httptest.NewRecorder()
|
||||
handler(c, r)
|
||||
content := c.Body.Bytes()
|
||||
result := c.Result()
|
||||
if result.StatusCode != 200 {
|
||||
w.WriteHeader(result.StatusCode)
|
||||
w.Write(content)
|
||||
return
|
||||
}
|
||||
w.Write(content)
|
||||
if d, err := time.ParseDuration(duration); err == nil {
|
||||
go CacheStorage.Set(r.RequestURI, content, d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -27,39 +27,25 @@ import (
|
|||
)
|
||||
|
||||
func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var notifiers []*notifier.Notification
|
||||
for _, n := range core.CoreApp.Notifications {
|
||||
notif := n.(notifier.Notifier)
|
||||
notifiers = append(notifiers, notif.Select())
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(notifiers)
|
||||
returnJson(notifiers, w, r)
|
||||
}
|
||||
|
||||
func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
_, notifierObj, err := notifier.SelectNotifier(vars["notifier"])
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(notifierObj)
|
||||
returnJson(notifierObj, w, r)
|
||||
}
|
||||
|
||||
func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
notifer, not, err := notifier.SelectNotifier(vars["notifier"])
|
||||
if err != nil {
|
||||
|
@ -83,10 +69,6 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
form := parseForm(r)
|
||||
vars := mux.Vars(r)
|
||||
method := vars["method"]
|
||||
|
|
|
@ -27,10 +27,6 @@ type PluginSelect struct {
|
|||
}
|
||||
|
||||
func pluginSavedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
//vars := mux.Vars(router)
|
||||
//plug := SelectPlugin(vars["name"])
|
||||
|
@ -43,11 +39,6 @@ func pluginSavedHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func pluginsDownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
auth := IsFullAuthenticated(r)
|
||||
if !auth {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
//vars := mux.Vars(router)
|
||||
//name := vars["name"]
|
||||
//DownloadPlugin(name)
|
||||
|
|
|
@ -33,10 +33,6 @@ import (
|
|||
//
|
||||
|
||||
func prometheusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
metrics := []string{}
|
||||
system := fmt.Sprintf("statping_total_failures %v\n", core.CountFailures())
|
||||
system += fmt.Sprintf("statping_total_services %v", len(core.CoreApp.Services))
|
||||
|
|
|
@ -17,9 +17,11 @@ package handlers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/handlers/graphql"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
|
@ -36,7 +38,7 @@ func Router() *mux.Router {
|
|||
dir := utils.Directory
|
||||
CacheStorage = NewStorage()
|
||||
r := mux.NewRouter()
|
||||
r.Handle("/", cached("60s", "text/html", http.HandlerFunc(indexHandler)))
|
||||
r.Handle("/", http.HandlerFunc(indexHandler))
|
||||
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"))))
|
||||
|
@ -59,85 +61,96 @@ func Router() *mux.Router {
|
|||
r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET")
|
||||
r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST")
|
||||
r.Handle("/logout", http.HandlerFunc(logoutHandler))
|
||||
r.Handle("/plugins/download/{name}", http.HandlerFunc(pluginsDownloadHandler))
|
||||
r.Handle("/plugins/{name}/save", http.HandlerFunc(pluginSavedHandler)).Methods("POST")
|
||||
r.Handle("/help", http.HandlerFunc(helpHandler))
|
||||
r.Handle("/logs", http.HandlerFunc(logsHandler))
|
||||
r.Handle("/logs/line", http.HandlerFunc(logsLineHandler))
|
||||
r.Handle("/plugins/download/{name}", authenticated(pluginsDownloadHandler, true))
|
||||
r.Handle("/plugins/{name}/save", authenticated(pluginSavedHandler, true)).Methods("POST")
|
||||
r.Handle("/help", authenticated(helpHandler, true))
|
||||
r.Handle("/logs", authenticated(logsHandler, true))
|
||||
r.Handle("/logs/line", readOnly(logsLineHandler, true))
|
||||
|
||||
// GRAPHQL Route
|
||||
r.Handle("/graphql", authenticated(handler.GraphQL(graphql.NewExecutableSchema(graphql.Config{Resolvers: &graphql.Resolver{}})), true))
|
||||
|
||||
// USER Routes
|
||||
r.Handle("/users", http.HandlerFunc(usersHandler)).Methods("GET")
|
||||
r.Handle("/user/{id}", http.HandlerFunc(usersEditHandler)).Methods("GET")
|
||||
r.Handle("/users", readOnly(usersHandler, true)).Methods("GET")
|
||||
r.Handle("/user/{id}", authenticated(usersEditHandler, true)).Methods("GET")
|
||||
|
||||
// MESSAGES Routes
|
||||
r.Handle("/messages", http.HandlerFunc(messagesHandler)).Methods("GET")
|
||||
r.Handle("/message/{id}", http.HandlerFunc(viewMessageHandler)).Methods("GET")
|
||||
r.Handle("/messages", authenticated(messagesHandler, true)).Methods("GET")
|
||||
r.Handle("/message/{id}", authenticated(viewMessageHandler, true)).Methods("GET")
|
||||
|
||||
// SETTINGS Routes
|
||||
r.Handle("/settings", http.HandlerFunc(settingsHandler)).Methods("GET")
|
||||
r.Handle("/settings", http.HandlerFunc(saveSettingsHandler)).Methods("POST")
|
||||
r.Handle("/settings/css", http.HandlerFunc(saveSASSHandler)).Methods("POST")
|
||||
r.Handle("/settings/build", http.HandlerFunc(saveAssetsHandler)).Methods("GET")
|
||||
r.Handle("/settings/delete_assets", http.HandlerFunc(deleteAssetsHandler)).Methods("GET")
|
||||
r.Handle("/settings/export", http.HandlerFunc(exportHandler)).Methods("GET")
|
||||
r.Handle("/settings", authenticated(settingsHandler, true)).Methods("GET")
|
||||
r.Handle("/settings", authenticated(saveSettingsHandler, true)).Methods("POST")
|
||||
r.Handle("/settings/css", authenticated(saveSASSHandler, true)).Methods("POST")
|
||||
r.Handle("/settings/build", authenticated(saveAssetsHandler, true)).Methods("GET")
|
||||
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")
|
||||
|
||||
// SERVICE Routes
|
||||
r.Handle("/services", http.HandlerFunc(servicesHandler)).Methods("GET")
|
||||
r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET")
|
||||
r.Handle("/service/{id}/edit", http.HandlerFunc(servicesViewHandler)).Methods("GET")
|
||||
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).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 GROUPS Routes
|
||||
r.Handle("/api/groups", http.HandlerFunc(apiAllGroupHandler)).Methods("GET")
|
||||
r.Handle("/api/groups", http.HandlerFunc(apiCreateGroupHandler)).Methods("POST")
|
||||
r.Handle("/api/groups/{id}", http.HandlerFunc(apiGroupHandler)).Methods("GET")
|
||||
r.Handle("/api/groups/{id}", http.HandlerFunc(apiGroupDeleteHandler)).Methods("DELETE")
|
||||
r.Handle("/api/groups/reorder", http.HandlerFunc(apiGroupReorderHandler)).Methods("POST")
|
||||
r.Handle("/api/groups", readOnly(apiAllGroupHandler, false)).Methods("GET")
|
||||
r.Handle("/api/groups", authenticated(apiCreateGroupHandler, false)).Methods("POST")
|
||||
r.Handle("/api/groups/{id}", readOnly(apiGroupHandler, false)).Methods("GET")
|
||||
r.Handle("/api/groups/{id}", authenticated(apiGroupUpdateHandler, false)).Methods("POST")
|
||||
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", http.HandlerFunc(apiIndexHandler))
|
||||
r.Handle("/api/renew", http.HandlerFunc(apiRenewHandler))
|
||||
r.Handle("/api/clear_cache", http.HandlerFunc(apiClearCacheHandler))
|
||||
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", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")
|
||||
r.Handle("/api/services", http.HandlerFunc(apiCreateServiceHandler)).Methods("POST")
|
||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceHandler)).Methods("GET")
|
||||
r.Handle("/api/services/reorder", http.HandlerFunc(reorderServiceHandler)).Methods("POST")
|
||||
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}", http.HandlerFunc(apiServiceUpdateHandler)).Methods("POST")
|
||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceDeleteHandler)).Methods("DELETE")
|
||||
r.Handle("/api/services/{id}/failures", http.HandlerFunc(apiServiceFailuresHandler)).Methods("GET")
|
||||
r.Handle("/api/services/{id}/failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("DELETE")
|
||||
r.Handle("/api/services/{id}/hits", http.HandlerFunc(apiServiceHitsHandler)).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")
|
||||
r.Handle("/api/services/{id}/failures", authenticated(servicesDeleteFailuresHandler, false)).Methods("DELETE")
|
||||
r.Handle("/api/services/{id}/hits", authenticated(apiServiceHitsHandler, false)).Methods("GET")
|
||||
|
||||
// API INCIDENTS Routes
|
||||
r.Handle("/api/incidents", readOnly(apiAllIncidentsHandler, false)).Methods("GET")
|
||||
|
||||
// API USER Routes
|
||||
r.Handle("/api/users", http.HandlerFunc(apiAllUsersHandler)).Methods("GET")
|
||||
r.Handle("/api/users", http.HandlerFunc(apiCreateUsersHandler)).Methods("POST")
|
||||
r.Handle("/api/users/{id}", http.HandlerFunc(apiUserHandler)).Methods("GET")
|
||||
r.Handle("/api/users/{id}", http.HandlerFunc(apiUserUpdateHandler)).Methods("POST")
|
||||
r.Handle("/api/users/{id}", http.HandlerFunc(apiUserDeleteHandler)).Methods("DELETE")
|
||||
r.Handle("/api/users", authenticated(apiAllUsersHandler, false)).Methods("GET")
|
||||
r.Handle("/api/users", authenticated(apiCreateUsersHandler, false)).Methods("POST")
|
||||
r.Handle("/api/users/{id}", authenticated(apiUserHandler, false)).Methods("GET")
|
||||
r.Handle("/api/users/{id}", authenticated(apiUserUpdateHandler, false)).Methods("POST")
|
||||
r.Handle("/api/users/{id}", authenticated(apiUserDeleteHandler, false)).Methods("DELETE")
|
||||
|
||||
// API NOTIFIER Routes
|
||||
r.Handle("/api/notifiers", http.HandlerFunc(apiNotifiersHandler)).Methods("GET")
|
||||
r.Handle("/api/notifier/{notifier}", http.HandlerFunc(apiNotifierGetHandler)).Methods("GET")
|
||||
r.Handle("/api/notifier/{notifier}", http.HandlerFunc(apiNotifierUpdateHandler)).Methods("POST")
|
||||
r.Handle("/api/notifier/{method}/test", http.HandlerFunc(testNotificationHandler)).Methods("POST")
|
||||
r.Handle("/api/notifiers", authenticated(apiNotifiersHandler, false)).Methods("GET")
|
||||
r.Handle("/api/notifier/{notifier}", authenticated(apiNotifierGetHandler, false)).Methods("GET")
|
||||
r.Handle("/api/notifier/{notifier}", authenticated(apiNotifierUpdateHandler, false)).Methods("POST")
|
||||
r.Handle("/api/notifier/{method}/test", authenticated(testNotificationHandler, false)).Methods("POST")
|
||||
|
||||
// API MESSAGES Routes
|
||||
r.Handle("/api/messages", http.HandlerFunc(apiAllMessagesHandler)).Methods("GET")
|
||||
r.Handle("/api/messages", http.HandlerFunc(apiMessageCreateHandler)).Methods("POST")
|
||||
r.Handle("/api/messages/{id}", http.HandlerFunc(apiMessageGetHandler)).Methods("GET")
|
||||
r.Handle("/api/messages/{id}", http.HandlerFunc(apiMessageUpdateHandler)).Methods("POST")
|
||||
r.Handle("/api/messages/{id}", http.HandlerFunc(apiMessageDeleteHandler)).Methods("DELETE")
|
||||
r.Handle("/api/messages", readOnly(apiAllMessagesHandler, false)).Methods("GET")
|
||||
r.Handle("/api/messages", authenticated(apiMessageCreateHandler, false)).Methods("POST")
|
||||
r.Handle("/api/messages/{id}", readOnly(apiMessageGetHandler, false)).Methods("GET")
|
||||
r.Handle("/api/messages/{id}", authenticated(apiMessageUpdateHandler, false)).Methods("POST")
|
||||
r.Handle("/api/messages/{id}", authenticated(apiMessageDeleteHandler, false)).Methods("DELETE")
|
||||
|
||||
// API CHECKIN Routes
|
||||
r.Handle("/api/checkins", http.HandlerFunc(apiAllCheckinsHandler)).Methods("GET")
|
||||
r.Handle("/api/checkin/{api}", http.HandlerFunc(apiCheckinHandler)).Methods("GET")
|
||||
r.Handle("/api/checkin", http.HandlerFunc(checkinCreateHandler)).Methods("POST")
|
||||
r.Handle("/api/checkin/{api}", http.HandlerFunc(checkinDeleteHandler)).Methods("DELETE")
|
||||
r.Handle("/api/checkins", authenticated(apiAllCheckinsHandler, false)).Methods("GET")
|
||||
r.Handle("/api/checkin/{api}", authenticated(apiCheckinHandler, false)).Methods("GET")
|
||||
r.Handle("/api/checkin", authenticated(checkinCreateHandler, false)).Methods("POST")
|
||||
r.Handle("/api/checkin/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
|
||||
r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler))
|
||||
|
||||
// Static Files Routes
|
||||
|
@ -146,7 +159,7 @@ func Router() *mux.Router {
|
|||
r.PathPrefix("/files/grafana.json").Handler(http.StripPrefix("/files/", http.FileServer(source.TmplBox.HTTPBox())))
|
||||
|
||||
// API Generic Routes
|
||||
r.Handle("/metrics", http.HandlerFunc(prometheusHandler))
|
||||
r.Handle("/metrics", readOnly(prometheusHandler, false))
|
||||
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
|
||||
r.Handle("/.well-known/", http.StripPrefix("/.well-known/", http.FileServer(http.Dir(dir+"/.well-known"))))
|
||||
|
||||
|
|
|
@ -49,10 +49,6 @@ func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func servicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsUser(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"Services": core.CoreApp.Services,
|
||||
}
|
||||
|
@ -65,10 +61,6 @@ type serviceOrder struct {
|
|||
}
|
||||
|
||||
func reorderServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
var newOrder []*serviceOrder
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
@ -78,8 +70,7 @@ func reorderServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
service.Order = s.Order
|
||||
service.Update(false)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(newOrder)
|
||||
returnJson(newOrder, w, r)
|
||||
}
|
||||
|
||||
func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -131,25 +122,16 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
servicer := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if servicer == nil {
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(servicer)
|
||||
returnJson(servicer, w, r)
|
||||
}
|
||||
|
||||
func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var service *types.Service
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&service)
|
||||
|
@ -167,10 +149,6 @@ func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
|
@ -188,6 +166,21 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendJsonAction(service, "update", w, r)
|
||||
}
|
||||
|
||||
func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
if service.IsRunning() {
|
||||
service.Close()
|
||||
} else {
|
||||
service.Start()
|
||||
}
|
||||
sendJsonAction(service, "running", w, r)
|
||||
}
|
||||
|
||||
func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
|
@ -207,8 +200,7 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
end := time.Unix(endField, 0)
|
||||
|
||||
obj := core.GraphDataRaw(service, start, end, grouping, "latency")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(obj)
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -227,9 +219,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
end := time.Unix(endField, 0)
|
||||
|
||||
obj := core.GraphDataRaw(service, start, end, grouping, "ping_time")
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(obj)
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
type dataXy struct {
|
||||
|
@ -285,16 +275,10 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
|
|||
month = 1
|
||||
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(monthOutput)
|
||||
returnJson(monthOutput, w, r)
|
||||
}
|
||||
|
||||
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
|
@ -310,20 +294,11 @@ func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiAllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
services := core.Services()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(services)
|
||||
returnJson(services, w, r)
|
||||
}
|
||||
|
||||
func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
|
@ -335,25 +310,16 @@ func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiServiceFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
servicer := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if servicer == nil {
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(servicer.AllFailures())
|
||||
returnJson(servicer.AllFailures(), w, r)
|
||||
}
|
||||
|
||||
func apiServiceHitsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsReadAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
servicer := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if servicer == nil {
|
||||
|
@ -367,6 +333,5 @@ func apiServiceHitsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(hits)
|
||||
returnJson(hits, w, r)
|
||||
}
|
||||
|
|
|
@ -16,73 +16,67 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func settingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, nil)
|
||||
}
|
||||
|
||||
func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var err error
|
||||
r.ParseForm()
|
||||
form := parseForm(r)
|
||||
app := core.CoreApp
|
||||
name := r.PostForm.Get("project")
|
||||
name := form.Get("project")
|
||||
if name != "" {
|
||||
app.Name = name
|
||||
}
|
||||
description := r.PostForm.Get("description")
|
||||
description := form.Get("description")
|
||||
if description != app.Description {
|
||||
app.Description = description
|
||||
}
|
||||
style := r.PostForm.Get("style")
|
||||
style := form.Get("style")
|
||||
if style != app.Style {
|
||||
app.Style = style
|
||||
}
|
||||
footer := r.PostForm.Get("footer")
|
||||
footer := form.Get("footer")
|
||||
if footer != app.Footer.String {
|
||||
app.Footer = types.NewNullString(footer)
|
||||
}
|
||||
domain := r.PostForm.Get("domain")
|
||||
domain := form.Get("domain")
|
||||
if domain != app.Domain {
|
||||
app.Domain = domain
|
||||
}
|
||||
timezone := r.PostForm.Get("timezone")
|
||||
timezone := form.Get("timezone")
|
||||
timeFloat, _ := strconv.ParseFloat(timezone, 10)
|
||||
app.Timezone = float32(timeFloat)
|
||||
|
||||
app.UseCdn = types.NewNullBool(r.PostForm.Get("enable_cdn") == "on")
|
||||
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()))
|
||||
}
|
||||
|
||||
|
||||
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
}
|
||||
|
||||
func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
form := r.PostForm
|
||||
form := parseForm(r)
|
||||
theme := form.Get("theme")
|
||||
variables := form.Get("variables")
|
||||
mobile := form.Get("mobile")
|
||||
|
@ -95,19 +89,13 @@ func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
dir := utils.Directory
|
||||
err := source.CreateAllAssets(dir)
|
||||
if err != nil {
|
||||
if err := source.CreateAllAssets(dir); err != nil {
|
||||
utils.Log(3, err)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
err = source.CompileSASS(dir)
|
||||
if err != nil {
|
||||
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.")
|
||||
}
|
||||
|
@ -116,18 +104,101 @@ func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
if err := source.DeleteAllAssets(utils.Directory); err != nil {
|
||||
utils.Log(3, fmt.Errorf("error deleting all assets %v", err))
|
||||
}
|
||||
source.DeleteAllAssets(utils.Directory)
|
||||
resetRouter()
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
}
|
||||
|
||||
func parseId(r *http.Request) int64 {
|
||||
vars := mux.Vars(r)
|
||||
return utils.ToInt(vars["id"])
|
||||
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))
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
io.Copy(&fileData, file)
|
||||
data := fileData.String()
|
||||
|
||||
for i, line := range strings.Split(strings.TrimSuffix(data, "\n"), "\n")[1:] {
|
||||
col := strings.Split(line, ",")
|
||||
|
||||
newService, err := commaToService(col)
|
||||
if err != nil {
|
||||
utils.Log(3, 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))
|
||||
continue
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Created new service %v", service.Name))
|
||||
}
|
||||
|
||||
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
|
||||
}
|
||||
|
||||
// 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) != 17 {
|
||||
err := fmt.Errorf("does not have the expected amount of %v columns for a service", 16)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval, err := time.ParseDuration(s[4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(s[9])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowNotifications, err := strconv.ParseBool(s[11])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
public, err := strconv.ParseBool(s[12])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verifySsl, err := strconv.ParseBool(s[16])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
func parseForm(r *http.Request) url.Values {
|
||||
|
|
|
@ -53,10 +53,11 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
project := r.PostForm.Get("project")
|
||||
username := r.PostForm.Get("username")
|
||||
password := r.PostForm.Get("password")
|
||||
//sample := r.PostForm.Get("sample_data")
|
||||
description := r.PostForm.Get("description")
|
||||
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{
|
||||
|
@ -117,7 +118,9 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
admin.Create()
|
||||
|
||||
core.SampleData()
|
||||
if sample {
|
||||
core.SampleData()
|
||||
}
|
||||
core.InitApp()
|
||||
CacheStorage.Delete("/")
|
||||
resetCookies()
|
||||
|
|
|
@ -28,19 +28,11 @@ import (
|
|||
)
|
||||
|
||||
func usersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsUser(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
users, _ := core.SelectAllUsers()
|
||||
ExecuteResponse(w, r, "users.gohtml", users, nil)
|
||||
}
|
||||
|
||||
func usersEditHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
user, _ := core.SelectUser(int64(id))
|
||||
|
@ -48,10 +40,6 @@ func usersEditHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
user, err := core.SelectUser(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
|
@ -59,15 +47,10 @@ func apiUserHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
user.Password = ""
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(user)
|
||||
returnJson(user, w, r)
|
||||
}
|
||||
|
||||
func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
user, err := core.SelectUser(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
|
@ -88,10 +71,6 @@ func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
users := core.CountUsers()
|
||||
if users == 1 {
|
||||
|
@ -112,24 +91,15 @@ func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
users, err := core.SelectAllUsers()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(users)
|
||||
returnJson(users, w, r)
|
||||
}
|
||||
|
||||
func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsFullAuthenticated(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var user *types.User
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&user)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
|
@ -75,23 +76,21 @@ 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(s.Id, u.Var2)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var2)
|
||||
}
|
||||
|
||||
// OnSuccess for commandLine will trigger successful service
|
||||
func (u *commandLine) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
u.AddQueue(s.Id, u.Var1)
|
||||
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
|
||||
func (u *commandLine) OnSave() error {
|
||||
u.AddQueue(0, u.Var1)
|
||||
u.AddQueue(0, u.Var2)
|
||||
u.AddQueue("saved", u.Var1)
|
||||
u.AddQueue("saved", u.Var2)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -66,33 +66,16 @@ func TestCommandNotifier(t *testing.T) {
|
|||
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 Check Offline", func(t *testing.T) {
|
||||
assert.False(t, command.Online)
|
||||
})
|
||||
|
||||
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)
|
||||
time.Sleep(20 * time.Second)
|
||||
assert.Equal(t, 0, len(command.Queue))
|
||||
})
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ func init() {
|
|||
// 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
|
||||
}
|
||||
|
||||
|
@ -70,24 +70,27 @@ func (u *discord) Select() *notifier.Notification {
|
|||
// OnFailure will trigger failing service
|
||||
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(s.Id, msg)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *discord) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
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
|
||||
func (u *discord) OnSave() error {
|
||||
msg := fmt.Sprintf(`{"content": "The discord notifier on Statping was just updated."}`)
|
||||
u.AddQueue(0, msg)
|
||||
u.AddQueue("saved", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -95,7 +98,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
|
||||
}
|
||||
|
|
|
@ -69,17 +69,13 @@ func TestDiscordNotifier(t *testing.T) {
|
|||
assert.Equal(t, 1, len(discorder.Queue))
|
||||
})
|
||||
|
||||
t.Run("discord Check Offline", func(t *testing.T) {
|
||||
assert.False(t, discorder.Online)
|
||||
})
|
||||
|
||||
t.Run("discord OnSuccess", func(t *testing.T) {
|
||||
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) {
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"html/template"
|
||||
"net/smtp"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -149,6 +148,12 @@ var emailer = &email{¬ifier.Notification{
|
|||
Title: "Send Alerts To",
|
||||
Placeholder: "sendto@email.com",
|
||||
DbField: "Var2",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "Disable TLS/SSL",
|
||||
Placeholder: "",
|
||||
SmallText: "To Disable TLS/SSL insert 'true'",
|
||||
DbField: "api_key",
|
||||
}},
|
||||
}}
|
||||
|
||||
|
@ -188,24 +193,28 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) {
|
|||
Data: interface{}(s),
|
||||
From: u.Var1,
|
||||
}
|
||||
u.AddQueue(s.Id, email)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), email)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *email) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
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(s.Id, email)
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), email)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
func (u *email) Select() *notifier.Notification {
|
||||
|
@ -221,20 +230,6 @@ func (u *email) OnSave() error {
|
|||
|
||||
// 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
|
||||
}
|
||||
err = dial.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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",
|
||||
|
@ -253,18 +248,22 @@ func (u *email) OnTest() error {
|
|||
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.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
emailSource(email)
|
||||
m := mail.NewMessage()
|
||||
// if email setting TLS is Disabled
|
||||
if u.ApiKey == "true" {
|
||||
mailer.SSL = false
|
||||
} else {
|
||||
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
m.SetHeader("From", email.From)
|
||||
m.SetHeader("To", email.To)
|
||||
m.SetHeader("Subject", email.Subject)
|
||||
|
|
|
@ -105,17 +105,13 @@ func TestEmailNotifier(t *testing.T) {
|
|||
assert.Equal(t, 1, len(emailer.Queue))
|
||||
})
|
||||
|
||||
t.Run("email Check Offline", func(t *testing.T) {
|
||||
assert.False(t, emailer.Online)
|
||||
})
|
||||
|
||||
t.Run("email OnSuccess", func(t *testing.T) {
|
||||
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) {
|
||||
|
@ -136,7 +132,7 @@ func TestEmailNotifier(t *testing.T) {
|
|||
|
||||
t.Run("email Run Queue", func(t *testing.T) {
|
||||
go notifier.Queue(emailer)
|
||||
time.Sleep(5 * time.Second)
|
||||
time.Sleep(6 * time.Second)
|
||||
assert.Equal(t, EMAIL_HOST, emailer.Host)
|
||||
assert.Equal(t, 0, len(emailer.Queue))
|
||||
})
|
||||
|
|
|
@ -62,7 +62,7 @@ func (u *lineNotifier) Send(msg interface{}) error {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -73,23 +73,27 @@ func (u *lineNotifier) Select() *notifier.Notification {
|
|||
// OnFailure will trigger failing service
|
||||
func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *lineNotifier) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
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))
|
||||
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(1, msg)
|
||||
u.AddQueue("saved", message)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ type mobilePush struct {
|
|||
var mobile = &mobilePush{¬ifier.Notification{
|
||||
Method: "mobile",
|
||||
Title: "Mobile Notifications",
|
||||
Description: `Receive push notifications on your Android or iPhone devices 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",
|
||||
|
@ -99,24 +99,28 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) {
|
|||
Topic: mobileIdentifier,
|
||||
Data: data,
|
||||
}
|
||||
u.AddQueue(s.Id, msg)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *mobilePush) OnSuccess(s *types.Service) {
|
||||
data := dataJson(s, nil)
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
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(s.Id, msg)
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
|
@ -126,7 +130,7 @@ func (u *mobilePush) OnSave() error {
|
|||
Title: "Notification Saved",
|
||||
Topic: mobileIdentifier,
|
||||
}
|
||||
u.AddQueue(0, msg)
|
||||
u.AddQueue("saved", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -178,7 +182,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
|
||||
}
|
||||
|
||||
|
|
|
@ -76,29 +76,20 @@ func TestMobileNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("mobile OnFailure multiple times", func(t *testing.T) {
|
||||
for i := 0; i <= 50; i++ {
|
||||
for i := 0; i <= 5; i++ {
|
||||
mobile.OnFailure(TestService, TestFailure)
|
||||
}
|
||||
assert.Equal(t, 52, len(mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile Check Offline", func(t *testing.T) {
|
||||
assert.False(t, mobile.Online)
|
||||
assert.Equal(t, 7, len(mobile.Queue))
|
||||
})
|
||||
|
||||
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))
|
||||
assert.Equal(t, 7, len(mobile.Queue))
|
||||
})
|
||||
|
||||
t.Run("mobile OnSuccess Again", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
assert.True(t, mobile.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
mobile.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(mobile.Queue))
|
||||
go notifier.Queue(mobile)
|
||||
|
|
|
@ -42,6 +42,7 @@ 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),
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ 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":"{{.}}"}`
|
||||
)
|
||||
|
@ -71,7 +71,7 @@ func parseSlackMessage(id int64, temp string, data interface{}) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slacker.AddQueue(id, buf.String())
|
||||
slacker.AddQueue(fmt.Sprintf("service_%v", id), buf.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -79,12 +79,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 +94,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")
|
||||
}
|
||||
|
@ -106,15 +107,15 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
|
|||
Service: s,
|
||||
Template: failingTemplate,
|
||||
Time: time.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 {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
if !s.Online {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
message := slackMessage{
|
||||
Service: s,
|
||||
Template: successTemplate,
|
||||
|
@ -122,12 +123,11 @@ func (u *slack) OnSuccess(s *types.Service) {
|
|||
}
|
||||
parseSlackMessage(s.Id, successTemplate, message)
|
||||
}
|
||||
u.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
func (u *slack) OnSave() error {
|
||||
message := fmt.Sprintf("Notification %v is receiving updated information.", u.Method)
|
||||
u.AddQueue(0, message)
|
||||
u.AddQueue("saved", message)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -78,33 +78,17 @@ func TestSlackNotifier(t *testing.T) {
|
|||
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)
|
||||
})
|
||||
|
||||
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))
|
||||
})
|
||||
|
||||
t.Run("slack OnSuccess Again", func(t *testing.T) {
|
||||
assert.True(t, slacker.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
slacker.OnSuccess(TestService)
|
||||
assert.Equal(t, 1, len(slacker.Queue))
|
||||
go notifier.Queue(slacker)
|
||||
time.Sleep(6 * time.Second)
|
||||
time.Sleep(15 * time.Second)
|
||||
assert.Equal(t, 0, len(slacker.Queue))
|
||||
})
|
||||
|
||||
|
@ -127,7 +111,7 @@ func TestSlackNotifier(t *testing.T) {
|
|||
|
||||
t.Run("slack Queue", func(t *testing.T) {
|
||||
go notifier.Queue(slacker)
|
||||
time.Sleep(5 * time.Second)
|
||||
time.Sleep(10 * time.Second)
|
||||
assert.Equal(t, SLACK_URL, slacker.Host)
|
||||
assert.Equal(t, 0, len(slacker.Queue))
|
||||
})
|
||||
|
|
|
@ -78,7 +78,7 @@ func (u *telegram) Send(msg interface{}) error {
|
|||
v.Set("text", message)
|
||||
rb := *strings.NewReader(v.Encode())
|
||||
|
||||
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second))
|
||||
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true)
|
||||
|
||||
success, _ := telegramSuccess(contents)
|
||||
if !success {
|
||||
|
@ -92,18 +92,21 @@ func (u *telegram) Send(msg interface{}) error {
|
|||
// OnFailure will trigger failing service
|
||||
func (u *telegram) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *telegram) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
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
|
||||
|
|
|
@ -37,6 +37,7 @@ func init() {
|
|||
}
|
||||
|
||||
func TestTelegramNotifier(t *testing.T) {
|
||||
t.SkipNow()
|
||||
t.Parallel()
|
||||
if telegramToken == "" || telegramChannel == "" {
|
||||
t.Log("Telegram notifier testing skipped, missing TELEGRAM_TOKEN and TELEGRAM_CHANNEL environment variable")
|
||||
|
@ -71,7 +72,7 @@ func TestTelegramNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Telegram Check Offline", func(t *testing.T) {
|
||||
assert.False(t, telegramNotifier.Online)
|
||||
assert.False(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("Telegram OnSuccess", func(t *testing.T) {
|
||||
|
@ -80,7 +81,7 @@ func TestTelegramNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Telegram Check Back Online", func(t *testing.T) {
|
||||
assert.True(t, telegramNotifier.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("Telegram OnSuccess Again", func(t *testing.T) {
|
||||
|
|
|
@ -89,7 +89,7 @@ func (u *twilio) Send(msg interface{}) error {
|
|||
v.Set("Body", message)
|
||||
rb := *strings.NewReader(v.Encode())
|
||||
|
||||
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second))
|
||||
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true)
|
||||
success, _ := twilioSuccess(contents)
|
||||
if !success {
|
||||
errorOut := twilioError(contents)
|
||||
|
@ -102,18 +102,21 @@ func (u *twilio) Send(msg interface{}) error {
|
|||
// OnFailure will trigger failing service
|
||||
func (u *twilio) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
u.Online = false
|
||||
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (u *twilio) OnSuccess(s *types.Service) {
|
||||
if !u.Online {
|
||||
u.ResetUniqueQueue(s.Id)
|
||||
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
|
||||
u.AddQueue(s.Id, msg)
|
||||
if !s.Online || !s.SuccessNotified {
|
||||
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
var msg string
|
||||
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
|
||||
|
|
|
@ -45,7 +45,6 @@ func init() {
|
|||
|
||||
func TestTwilioNotifier(t *testing.T) {
|
||||
t.SkipNow()
|
||||
t.Parallel()
|
||||
if TWILIO_SID == "" || TWILIO_SECRET == "" || TWILIO_FROM == "" {
|
||||
t.Log("twilio notifier testing skipped, missing TWILIO_SID environment variable")
|
||||
t.SkipNow()
|
||||
|
@ -77,7 +76,7 @@ func TestTwilioNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Twilio Check Offline", func(t *testing.T) {
|
||||
assert.False(t, twilioNotifier.Online)
|
||||
assert.False(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("Twilio OnSuccess", func(t *testing.T) {
|
||||
|
@ -86,7 +85,7 @@ func TestTwilioNotifier(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Twilio Check Back Online", func(t *testing.T) {
|
||||
assert.True(t, twilioNotifier.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("Twilio OnSuccess Again", func(t *testing.T) {
|
||||
|
|
|
@ -100,14 +100,8 @@ func (w *webhooker) Select() *notifier.Notification {
|
|||
}
|
||||
|
||||
func replaceBodyText(body string, s *types.Service, f *types.Failure) string {
|
||||
if s != nil {
|
||||
body = strings.Replace(body, "%service.Name", s.Name, -1)
|
||||
body = strings.Replace(body, "%service.Id", utils.ToString(s.Id), -1)
|
||||
body = strings.Replace(body, "%service.Online", utils.ToString(s.Online), -1)
|
||||
}
|
||||
if strings.Contains(body, "%failure.Issue") && f != nil {
|
||||
body = strings.Replace(body, "%failure.Issue", f.Issue, -1)
|
||||
}
|
||||
body = utils.ConvertInterface(body, s)
|
||||
body = utils.ConvertInterface(body, f)
|
||||
return body
|
||||
}
|
||||
|
||||
|
@ -171,18 +165,16 @@ func (w *webhooker) OnTest() error {
|
|||
// OnFailure will trigger failing service
|
||||
func (w *webhooker) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := replaceBodyText(w.Var2, s, f)
|
||||
w.AddQueue(s.Id, msg)
|
||||
w.Online = false
|
||||
w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (w *webhooker) OnSuccess(s *types.Service) {
|
||||
if !w.Online {
|
||||
w.ResetUniqueQueue(s.Id)
|
||||
if !s.Online {
|
||||
w.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
|
||||
msg := replaceBodyText(w.Var2, s, nil)
|
||||
w.AddQueue(s.Id, msg)
|
||||
w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
|
||||
}
|
||||
w.Online = true
|
||||
}
|
||||
|
||||
// OnSave triggers when this notifier has been saved
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestWebhookNotifier(t *testing.T) {
|
|||
|
||||
t.Run("webhooker Replace Body Text", func(t *testing.T) {
|
||||
fullMsg = replaceBodyText(webhookMessage, TestService, TestFailure)
|
||||
assert.Equal(t, `{"id": "1","name": "Interpol - All The Rage Back Home","online": "false","issue": "testing"}`, fullMsg)
|
||||
assert.Equal(t, `{"id": "1","name": "Interpol - All The Rage Back Home","online": "true","issue": "testing"}`, fullMsg)
|
||||
})
|
||||
|
||||
t.Run("webhooker Within Limits", func(t *testing.T) {
|
||||
|
@ -79,17 +79,13 @@ func TestWebhookNotifier(t *testing.T) {
|
|||
assert.Len(t, webhook.Queue, 1)
|
||||
})
|
||||
|
||||
t.Run("webhooker Check Offline", func(t *testing.T) {
|
||||
assert.False(t, webhook.Online)
|
||||
})
|
||||
|
||||
t.Run("webhooker OnSuccess", func(t *testing.T) {
|
||||
webhook.OnSuccess(TestService)
|
||||
assert.Equal(t, len(webhook.Queue), 1)
|
||||
})
|
||||
|
||||
t.Run("webhooker Check Back Online", func(t *testing.T) {
|
||||
assert.True(t, webhook.Online)
|
||||
assert.True(t, TestService.Online)
|
||||
})
|
||||
|
||||
t.Run("webhooker OnSuccess Again", func(t *testing.T) {
|
||||
|
|
|
@ -125,6 +125,43 @@ HTML, BODY {
|
|||
height: 300px;
|
||||
width: 100%; }
|
||||
|
||||
.inputTags-field {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
padding-top: .13rem; }
|
||||
|
||||
input.inputTags-field:focus {
|
||||
outline-width: 0; }
|
||||
|
||||
.inputTags-list {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: calc(2.25rem + 2px);
|
||||
padding: .2rem .35rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; }
|
||||
|
||||
.inputTags-item {
|
||||
background-color: #3aba39;
|
||||
margin-right: 5px;
|
||||
padding: 5px 8px;
|
||||
font-size: 10pt;
|
||||
color: white;
|
||||
border-radius: 4px; }
|
||||
|
||||
.inputTags-item .close-item {
|
||||
margin-left: 6px;
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
cursor: pointer; }
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3e9bff;
|
||||
border-color: #006fe6;
|
||||
|
@ -361,13 +398,13 @@ HTML, BODY {
|
|||
.pulse-glow:before,
|
||||
.pulse-glow:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 0.5rem;
|
||||
width: 1.75rem;
|
||||
top: 1.2rem;
|
||||
content: "";
|
||||
height: 0.4rem;
|
||||
width: 1.7rem;
|
||||
top: 1.3rem;
|
||||
right: 2.15rem;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 0 7px #47d337;
|
||||
box-shadow: 0 0 6px #47d337;
|
||||
animation: glow-grow 2s ease-out infinite; }
|
||||
|
||||
.sortable_drag {
|
||||
|
@ -415,6 +452,12 @@ HTML, BODY {
|
|||
.jumbotron {
|
||||
background-color: white; }
|
||||
|
||||
.toggle-service {
|
||||
font-size: 18pt;
|
||||
float: left;
|
||||
margin: 2px 3px 0 0;
|
||||
cursor: pointer; }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
HTML, BODY {
|
||||
background-color: #fcfcfc; }
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -111,32 +111,7 @@ let options = {
|
|||
series: [
|
||||
{
|
||||
name: "Response Time",
|
||||
data: [
|
||||
{
|
||||
x: "02-10-2017 GMT",
|
||||
y: 34
|
||||
},
|
||||
{
|
||||
x: "02-11-2017 GMT",
|
||||
y: 43
|
||||
},
|
||||
{
|
||||
x: "02-12-2017 GMT",
|
||||
y: 31
|
||||
},
|
||||
{
|
||||
x: "02-13-2017 GMT",
|
||||
y: 43
|
||||
},
|
||||
{
|
||||
x: "02-14-2017 GMT",
|
||||
y: 33
|
||||
},
|
||||
{
|
||||
x: "02-15-2017 GMT",
|
||||
y: 52
|
||||
}
|
||||
]
|
||||
data: [],
|
||||
}
|
||||
],
|
||||
xaxis: {
|
||||
|
@ -152,10 +127,12 @@ const startOn = UTCTime() - (86400 * 14);
|
|||
|
||||
async function RenderCharts() {
|
||||
{{ range .Services }}
|
||||
let chart{{.Id}} = new ApexCharts(document.querySelector("#service_{{js .Id}}"), options);{{end}}
|
||||
options.fill.colors = {{if .Online}}["#48d338"]{{else}}["#dd3545"]{{end}};
|
||||
options.stroke.colors = {{if .Online}}["#3aa82d"]{{else}}["#c23342"]{{end}};
|
||||
|
||||
{{ range .Services }}
|
||||
await RenderChart(chart{{js .Id}}, {{js .Id}}, startOn);{{end}}
|
||||
let chart{{.Id}} = new ApexCharts(document.querySelector("#service_{{js .Id}}"), options);
|
||||
|
||||
await RenderChart(chart{{js .Id}}, {{js .Id}}, startOn);{{end}}
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -86,6 +86,29 @@ $('.scrollclick').on('click',function(e) {
|
|||
e.preventDefault();
|
||||
});
|
||||
|
||||
$('.toggle-service').on('click',function(e) {
|
||||
let obj = $(this);
|
||||
let serviceId = obj.attr("data-id");
|
||||
let online = obj.attr("data-online");
|
||||
let d = confirm("Do you want to "+(eval(online) ? "stop" : "start")+" checking this service?");
|
||||
if (d) {
|
||||
$.ajax({
|
||||
url: "/api/services/" + serviceId + "/running",
|
||||
type: 'POST',
|
||||
success: function (data) {
|
||||
if (online === "true") {
|
||||
obj.removeClass("fa-toggle-on text-success");
|
||||
obj.addClass("fa-toggle-off text-black-50");
|
||||
} else {
|
||||
obj.removeClass("fa-toggle-off text-black-50");
|
||||
obj.addClass("fa-toggle-on text-success");
|
||||
}
|
||||
obj.attr("data-online", online !== "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('select#service_type').on('change', function() {
|
||||
var selected = $('#service_type option:selected').val();
|
||||
var typeLabel = $('#service_type_label');
|
||||
|
@ -100,6 +123,15 @@ $('select#service_type').on('change', function() {
|
|||
$('#service_url').attr('placeholder', '192.168.1.1');
|
||||
$('#post_data').parent().parent().addClass('d-none');
|
||||
$('#service_response').parent().parent().addClass('d-none');
|
||||
$('#service_response_code').parent().parent().addClass('d-none');
|
||||
$('#headers').parent().parent().addClass('d-none');
|
||||
} else if (selected === 'icmp') {
|
||||
$('#service_port').parent().parent().removeClass('d-none');
|
||||
$('#headers').parent().parent().addClass('d-none');
|
||||
$('#service_check_type').parent().parent().addClass('d-none');
|
||||
$('#service_url').attr('placeholder', '192.168.1.1');
|
||||
$('#post_data').parent().parent().addClass('d-none');
|
||||
$('#service_response').parent().parent().addClass('d-none');
|
||||
$('#service_response_code').parent().parent().addClass('d-none');
|
||||
} else {
|
||||
$('#post_data').parent().parent().removeClass('d-none');
|
||||
|
@ -113,6 +145,9 @@ $('select#service_type').on('change', function() {
|
|||
|
||||
|
||||
async function RenderChart(chart, service, start=0, end=9999999999, group="hour", retry=true) {
|
||||
if (!chart.el) {
|
||||
return
|
||||
}
|
||||
let chartData = await ChartLatency(service, start, end, group, retry);
|
||||
if (!chartData) {
|
||||
chartData = await ChartLatency(service, start, end, "minute", retry);
|
||||
|
@ -279,7 +314,6 @@ $('form.ajax_form').on('submit', function() {
|
|||
arrayData.push(newArr)
|
||||
});
|
||||
let sendData = JSON.stringify(newArr);
|
||||
// console.log('sending '+method.toUpperCase()+' '+action+':', sendData);
|
||||
$.ajax({
|
||||
url: action,
|
||||
type: method,
|
||||
|
@ -371,8 +405,24 @@ $(function() {
|
|||
|
||||
$('.confirm-btn').on('click', function() {
|
||||
var r = confirm('Are you sure you want to delete?');
|
||||
let obj = $(this);
|
||||
let redirect = obj.attr('data-redirect');
|
||||
let href = obj.attr('href');
|
||||
let method = obj.attr('data-method');
|
||||
let data = obj.attr('data-object');
|
||||
if (r === true) {
|
||||
return true;
|
||||
$.ajax({
|
||||
url: href,
|
||||
type: method,
|
||||
data: data ? data : null,
|
||||
success: function (data) {
|
||||
console.log("send to url: ", href);
|
||||
if (redirect) {
|
||||
window.location.href = redirect;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -151,6 +151,48 @@ HTML,BODY {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.inputTags-field {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
padding-top: .13rem;
|
||||
}
|
||||
|
||||
input.inputTags-field:focus {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
.inputTags-list {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: calc(2.25rem + 2px);
|
||||
padding: .2rem .35rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
|
||||
.inputTags-item {
|
||||
background-color: #3aba39;
|
||||
margin-right: 5px;
|
||||
padding: 5px 8px;
|
||||
font-size: 10pt;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.inputTags-item .close-item {
|
||||
margin-left: 6px;
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@mixin dynamic-color-hov($color) {
|
||||
&.dyn-dark {
|
||||
background-color: darken($color, 12%) !important;
|
||||
|
@ -417,15 +459,15 @@ HTML,BODY {
|
|||
|
||||
.pulse-glow:before,
|
||||
.pulse-glow:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 0.5rem;
|
||||
width: 1.75rem;
|
||||
top: 1.2rem;
|
||||
right: 2.15rem;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 0 7px #47d337;
|
||||
animation: glow-grow 2s ease-out infinite;
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 0.4rem;
|
||||
width: 1.7rem;
|
||||
top: 1.3rem;
|
||||
right: 2.15rem;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 0 6px #47d337;
|
||||
animation: glow-grow 2s ease-out infinite;
|
||||
}
|
||||
|
||||
.sortable_drag {
|
||||
|
@ -480,4 +522,11 @@ HTML,BODY {
|
|||
background-color: white;
|
||||
}
|
||||
|
||||
.toggle-service {
|
||||
font-size: 18pt;
|
||||
float: left;
|
||||
margin: 2px 3px 0 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@import 'mobile';
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
name,domain,expected,expected_status,interval,type,method,post_data,port,timeout,order,allow_notifications,public,group_id,headers,permalink
|
||||
Bulk Upload,http://google.com,,200,60s,http,get,,,60s,1,TRUE,TRUE,,Authorization=example,bulk_example
|
||||
JSON Post,https://jsonplaceholder.typicode.com/posts,,200,1m,http,post,"{""id"": 1, ""title"": 'foo', ""body"": 'bar', ""userId"": 1}",,15s,2,TRUE,TRUE,,Content-Type=application/json,json_post_example
|
||||
Google DNS,8.8.8.8,,,,tcp,,,53,10s,3,TRUE,TRUE,,,google_dns_example
|
||||
Google DNS UDP,8.8.8.8,,,,udp,,,53,10s,4,TRUE,TRUE,,,google_dns_udp_example
|
|
|
@ -17,9 +17,9 @@
|
|||
<label for="order" class="col-sm-4 col-form-label">Public Group</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="public" class="switch" id="switch-group-public" checked>
|
||||
<input type="checkbox" name="public" class="switch" id="switch-group-public" {{if .Public.Bool}}checked{{end}}>
|
||||
<label for="switch-group-public">Show group services to the public</label>
|
||||
<input type="hidden" name="public" id="switch-group-public-value" value="true">
|
||||
<input type="hidden" name="public" id="switch-group-public-value" value="{{if .Public.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
{{define "form_incident"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{$message := .}}
|
||||
{{if ne .Id 0}}
|
||||
<form class="ajax_form" action="/api/messages/{{.Id}}" data-redirect="/messages" method="POST">
|
||||
{{else}}
|
||||
<form class="ajax_form" action="/api/messages" data-redirect="/messages" method="POST">
|
||||
{{end}}
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="title" class="form-control" value="{{.Title}}" id="title" placeholder="Message Title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Description</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea rows="5" name="description" class="form-control" id="description" required>{{.Description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Message Date Range</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="{{ParseTime .StartOn "2006-01-02T15:04:05Z"}}" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="{{ParseTime .EndOn "2006-01-02T15:04:05Z"}}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control" name="service" id="service_id">
|
||||
<option value="0" {{if eq (ToString .ServiceId) "0"}}selected{{end}}>Global Message</option>
|
||||
{{range Services}}
|
||||
{{$s := .Select}}
|
||||
<option value="{{$s.Id}}" {{if eq $message.ServiceId $s.Id}}selected{{end}}>{{$s.Name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="notify_method" class="form-control" id="notify_method" value="{{.NotifyMethod}}" placeholder="email">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
|
||||
<div class="col-sm-8">
|
||||
<span class="switch">
|
||||
<input type="checkbox" name="notify_users-value" class="switch" id="switch-normal"{{if .NotifyUsers.Bool}} checked{{end}}>
|
||||
<label for="switch-normal">Notify Users Before Scheduled Time</label>
|
||||
<input type="hidden" name="notify_users" id="switch-normal-value" value="{{if .NotifyUsers.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="form-inline">
|
||||
<input type="number" name="notify_before" class="col-4 form-control" id="notify_before" value="{{.NotifyBefore.Int64}}">
|
||||
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
|
||||
<option value="minute"{{if ne .Id 0}} selected{{else}}{{if eq .NotifyBeforeScale "minute"}}selected{{end}}{{end}}>Minutes</option>
|
||||
<option value="hour"{{if eq .NotifyBeforeScale "hour"}} selected{{end}}>Hours</option>
|
||||
<option value="day"{{if eq .NotifyBeforeScale "day"}} selected{{end}}>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update Message{{else}}Create Message{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -21,6 +21,7 @@
|
|||
<option value="http" {{if eq .Type "http"}}selected{{end}}>HTTP Service</option>
|
||||
<option value="tcp" {{if eq .Type "tcp"}}selected{{end}}>TCP Service</option>
|
||||
<option value="udp" {{if eq .Type "udp"}}selected{{end}}>UDP Service</option>
|
||||
<option value="icmp" {{if eq .Type "icmp"}}selected{{end}}>ICMP Ping</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Use HTTP if you are checking a website or use TCP if you are checking a server</small>
|
||||
</div>
|
||||
|
@ -52,6 +53,13 @@
|
|||
<small class="form-text text-muted">Insert a JSON string to send data to the endpoint.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||
<label for="headers" class="col-sm-4 col-form-label">HTTP Headers</label>
|
||||
<div class="col-sm-8">
|
||||
<input name="headers" class="form-control" id="headers" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json' value="{{.Headers.String}}">
|
||||
<small class="form-text text-muted">Comma delimited list of HTTP Headers (KEY=VALUE,KEY=VALUE)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
||||
<div class="col-sm-8">
|
||||
|
@ -66,7 +74,7 @@
|
|||
<small class="form-text text-muted">A status code of 200 is success, or view all the <a target="_blank" href="https://www.restapitutorial.com/httpstatuscodes.html">HTTP Status Codes</a></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (ne .Type "tcp") and (ne .Type "udp")}} d-none{{end}}">
|
||||
<div class="form-group row{{if (ne .Type "tcp") and (ne .Type "udp") and (ne .Type "icmp")}} d-none{{end}}">
|
||||
<label for="port" class="col-sm-4 col-form-label">TCP Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080">
|
||||
|
@ -100,13 +108,23 @@
|
|||
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" {{if eq .Id 0}}checked{{end}}{{if .VerifySSL.Bool}}checked{{end}}>
|
||||
<label for="switch-verify-ssl">Verify SSL Certificate for this service</label>
|
||||
<input type="hidden" name="verify_ssl" id="switch-verify-ssl-value" value="{{if eq .Id 0}}true{{else}}{{if .VerifySSL.Bool}}true{{else}}false{{end}}{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" {{if eq .Id 0}}checked{{end}}{{if .AllowNotifications.Bool}}checked{{end}}>
|
||||
<label for="switch-notifications">Allow notifications to be sent for this service</label>
|
||||
<input type="hidden" name="allow_notifications" id="switch-notifications-value" value="{{if .AllowNotifications.Bool}}true{{else}}false{{end}}">
|
||||
<input type="hidden" name="allow_notifications" id="switch-notifications-value" value="{{if eq .Id 0}}true{{else}}{{if .AllowNotifications.Bool}}true{{else}}false{{end}}{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -116,7 +134,7 @@
|
|||
<span class="switch float-left">
|
||||
<input type="checkbox" name="public-option" class="switch" id="switch-public" {{if eq .Id 0}}checked{{else}}{{if .Public.Bool}}checked{{end}}{{end}}>
|
||||
<label for="switch-public">Show service details to the public</label>
|
||||
<input type="hidden" name="public" id="switch-public-value" value="{{if .Public.Bool}}true{{else}}false{{end}}">
|
||||
<input type="hidden" name="public" id="switch-public-value" value="{{if eq .Id 0}}true{{else}}{{if .Public.Bool}}true{{else}}false{{end}}{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -138,7 +156,7 @@
|
|||
</div>
|
||||
{{if ne .Id 0}}
|
||||
<div class="col-6">
|
||||
<a href="/service/{{ .Id }}/delete_failures" class="btn btn-danger btn-block confirm-btn">Delete All Failures</a>
|
||||
<a href="/service/{{ .Id }}/delete_failures" data-method="GET" data-redirect="/service/{{ .Id }}" class="btn btn-danger btn-block confirm-btn">Delete All Failures</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{{define "title"}}{{.Name}} Status{{end}}
|
||||
{{define "description"}}Group {{.Name}}{{end}}
|
||||
{{ define "content" }}
|
||||
|
||||
{{$isAdmin := Auth}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
|
||||
{{if IsUser}}
|
||||
{{template "nav"}}
|
||||
{{end}}
|
||||
|
||||
<div class="col-12 mb-4">
|
||||
{{template "form_group" .Group}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{end}}
|
|
@ -13,7 +13,7 @@
|
|||
<div class="col-12 full-col-12">
|
||||
<h4 class="group_header">{{.Name}}</h4>
|
||||
<div class="list-group online_list mb-3">
|
||||
{{ range .Services }}
|
||||
{{ range VisibleGroupServices . }}
|
||||
<a href="#" class="service_li list-group-item list-group-item-action {{if not .Online}}bg-danger text-white{{ end }}" data-id="{{.Id}}">
|
||||
{{ .Name }}
|
||||
{{if .Online}}
|
||||
|
@ -53,7 +53,7 @@
|
|||
</div>
|
||||
{{end}}
|
||||
|
||||
{{ range .Services }}
|
||||
{{ range VisibleServices }}
|
||||
{{$avgTime := .AvgTime}}
|
||||
<div class="mb-4" id="service_id_{{.Id}}">
|
||||
<div class="card">
|
||||
|
|
|
@ -1081,7 +1081,7 @@
|
|||
"exec": [
|
||||
"pm.test(\"View All Groups\", function () {",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.length).to.eql(2);",
|
||||
" pm.expect(jsonData.length).to.eql(3);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
|
@ -1365,6 +1365,57 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Reorder Groups",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "b5a67a19-fd08-40b0-a961-3e9474ab78c6",
|
||||
"exec": [
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{api_key}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "[{\"group\":1,\"order\":1},{\"group\":2,\"order\":2}]"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{endpoint}}/api/groups/reorder",
|
||||
"host": [
|
||||
"{{endpoint}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"groups",
|
||||
"reorder"
|
||||
]
|
||||
},
|
||||
"description": "Reorder services in a specific order for the index page."
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Delete Group",
|
||||
"event": [
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
{{ define "content" }}
|
||||
{{$s := .Service}}
|
||||
{{$failures := $s.LimitedFailures 16}}
|
||||
{{$incidents := $s.Incidents}}
|
||||
{{$checkinFailures := $s.LimitedCheckinFailures 16}}
|
||||
{{$isAdmin := Auth}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
|
@ -83,6 +84,7 @@
|
|||
<nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs" role="serviceLists">
|
||||
{{if $isAdmin}}<a class="flex-sm-fill text-sm-center nav-link active" id="edit-tab" data-toggle="tab" href="#edit" role="tab" aria-controls="edit" aria-selected="false">Edit Service</a>{{end}}
|
||||
<a class="flex-sm-fill text-sm-center nav-link{{ if not $failures }} disabled{{end}}" id="failures-tab" data-toggle="tab" href="#failures" role="tab" aria-controls="failures" aria-selected="true">Failures</a>
|
||||
<a class="flex-sm-fill text-sm-center nav-link{{ if not $incidents }} disabled{{end}}" id="incidents-tab" data-toggle="tab" href="#incidents" role="tab" aria-controls="incidents" aria-selected="true">Incidents</a>
|
||||
{{if $isAdmin}}<a class="flex-sm-fill text-sm-center nav-link" id="checkins-tab" data-toggle="tab" href="#checkins" role="tab" aria-controls="checkins" aria-selected="false">Checkins</a>{{end}}
|
||||
<a class="flex-sm-fill text-sm-center nav-link{{if not $isAdmin}} active{{end}}" id="response-tab" data-toggle="tab" href="#response" role="tab" aria-controls="response" aria-selected="false">Response</a>
|
||||
</nav>
|
||||
|
@ -104,6 +106,36 @@
|
|||
{{ end }}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="tab-pane fade" id="incidents" role="serviceLists" aria-labelledby="incidents-tab">
|
||||
{{ if $incidents }}
|
||||
<div class="list-group mt-3 mb-4">
|
||||
{{ range $incidents }}
|
||||
<div class="list-group-item flex-column align-items-start">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">{{.Title}}</h5>
|
||||
<small>{{.CreatedAt}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{.Description}}</p>
|
||||
|
||||
<ul class="list-group mt-3">
|
||||
{{ range .AllUpdates }}
|
||||
<li class="list-group-item">
|
||||
<p>
|
||||
<span class="badge badge-primary">{{.Type}}</span>
|
||||
<span class="float-right">
|
||||
{{.Message}}
|
||||
<p class="text-muted text-right small">{{.CreatedAt}}</p>
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{if $isAdmin}}
|
||||
<div class="tab-pane fade" id="checkins" role="serviceLists" aria-labelledby="checkins-tab">
|
||||
{{if $s.AllCheckins}}
|
||||
|
@ -303,11 +335,14 @@ async function RenderHeatmap() {
|
|||
}
|
||||
|
||||
async function RenderChartLatency() {
|
||||
options.fill.colors = {{if $s.Online}}["#48d338"]{{else}}["#dd3545"]{{end}};
|
||||
options.stroke.colors = {{if $s.Online}}["#3aa82d"]{{else}}["#c23342"]{{end}};
|
||||
let chart = new ApexCharts(document.querySelector("#service"), options);
|
||||
await RenderChart(chart,{{$s.Id}},{{.StartUnix}},{{.EndUnix}},"hour");
|
||||
}
|
||||
|
||||
$(document).ready(async function() {
|
||||
|
||||
let startDate = $("#service_start").flatpickr({
|
||||
enableTime: false,
|
||||
static: true,
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">Status</th>
|
||||
<th scope="col">Visibility</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">Visibility</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">Group</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -19,8 +20,11 @@
|
|||
{{range .Services}}
|
||||
<tr id="service_{{.Id}}" data-id="{{.Id}}">
|
||||
<td><span class="drag_icon d-none d-md-inline"><i class="fas fa-bars"></i></span> {{.Name}}</td>
|
||||
<td class="d-none d-md-table-cell">{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}}</td>
|
||||
<td class="d-none d-md-table-cell">{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}}
|
||||
<i class="toggle-service fas {{if .IsRunning}}fa-toggle-on text-success{{else}}fa-toggle-off text-muted{{end}}" data-online="{{if .IsRunning}}true{{else}}false{{end}}" data-id="{{.Id}}"></i>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">{{if .Public.Bool}}<span class="badge badge-primary">PUBLIC</span>{{else}}<span class="badge badge-secondary">PRIVATE</span>{{end}}</td>
|
||||
<td class="d-none d-md-table-cell">{{if ne .GroupId 0}}<span class="badge badge-secondary">{{(Group .GroupId).Name}}</span>{{end}}</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/service/{{ServiceLink .}}" class="btn btn-outline-secondary"><i class="fas fa-chart-area"></i> View</a>
|
||||
|
@ -57,6 +61,7 @@
|
|||
<td>{{if .Public.Bool}}<span class="badge badge-primary">PUBLIC</span>{{else}}<span class="badge badge-secondary">PRIVATE</span>{{end}}</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/group/{{.Id}}" class="btn btn-outline-secondary"><i class="fas fa-chart-area"></i> Edit</a>
|
||||
{{if Auth}}<a href="/api/groups/{{.Id}}" class="ajax_delete btn btn-danger" data-method="DELETE" data-obj="group_{{.Id}}" data-id="{{.Id}}"><i class="fas fa-times"></i></a>{{end}}
|
||||
</div>
|
||||
</td>
|
||||
|
@ -79,6 +84,7 @@
|
|||
<script src="/js/sortable.min.js"></script>
|
||||
{{end}}
|
||||
<script>
|
||||
// drag and drop sorting for Services
|
||||
sortable('.sortable', {
|
||||
forcePlaceholderSize: true,
|
||||
hoverClass: 'sortable_drag',
|
||||
|
@ -95,14 +101,16 @@
|
|||
newOrder.push(o);
|
||||
});
|
||||
$.ajax({
|
||||
url: "/api/services/reorder",
|
||||
type: 'POST',
|
||||
url: "/api/reorder/services",
|
||||
type: "POST",
|
||||
data: JSON.stringify(newOrder),
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
success: function(data) { }
|
||||
});
|
||||
});
|
||||
|
||||
// drag and drop sorting for Groups
|
||||
sortable('.sortable_groups', {
|
||||
forcePlaceholderSize: true,
|
||||
hoverClass: 'sortable_drag',
|
||||
|
@ -119,8 +127,8 @@
|
|||
newOrder.push(o);
|
||||
});
|
||||
$.ajax({
|
||||
url: "/api/groups/reorder",
|
||||
type: 'POST',
|
||||
url: "/api/reorder/groups",
|
||||
type: "POST",
|
||||
data: JSON.stringify(newOrder),
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
|
|
|
@ -37,6 +37,20 @@
|
|||
<input type="text" name="description" class="form-control" value="{{ .Description }}" id="description" placeholder="Great Uptime">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-4 mt-sm-1 mt-0">
|
||||
<label for="update_notify" class="d-inline d-sm-none">Send Updates only</label>
|
||||
<label for="update_notify" class="d-none d-sm-block">Send Updates only</label>
|
||||
|
||||
<span class="switch">
|
||||
<input type="checkbox" name="update_notify-option" class="switch" id="switch-update_notify"{{if UPDATENOTIFY}} checked{{end}}>
|
||||
<label for="switch-update_notify" class="mt-2 mt-sm-0"></label>
|
||||
</span>
|
||||
|
||||
<input type="hidden" name="update_notify" id="switch-update_notify-value" value="{{if UPDATENOTIFY}}true{{else}}false{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-8 col-sm-9">
|
||||
<label for="domain">Domain</label>
|
||||
|
@ -55,7 +69,7 @@
|
|||
|
||||
{{if not .Domain}}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Your Statup server does not have a dedicated URL!
|
||||
Your Statping server does not have a dedicated URL!
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
@ -123,14 +137,31 @@
|
|||
|
||||
</form>
|
||||
|
||||
<h3>Additional Settings</h3>
|
||||
<h3 class="mt-4">Bulk Import Services</h3>
|
||||
You can import multiple services based on a CSV file with the format shown on the <a href="https://github.com/hunterlong/statping/wiki/Bulk-Import-Services" target="_blank">Bulk Import Wiki</a>.
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="/settings/bulk_import" method="POST" enctype="multipart/form-data" class="form-inline">
|
||||
<div class="form-group col-10">
|
||||
<input type="file" name="file" class="form-control-file" accept=".csv">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-outline-success right">Import</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h3 class="mt-4">Additional Settings</h3>
|
||||
|
||||
<div class="row">
|
||||
<a href="/settings/export" class="btn btn-sm btn-secondary float-right">Export Settings</a>
|
||||
<div class="col-12">
|
||||
<a href="/settings/export" class="btn btn-sm btn-secondary float-right">Export Settings</a>
|
||||
{{if .Domain}}
|
||||
<a href="#" class="btn btn-sm btn-secondary float-right ml-1">Authentication QR Code</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Domain}}
|
||||
<div class="row align-content-center">
|
||||
|
@ -139,6 +170,8 @@
|
|||
<a class="btn btn-sm btn-primary" href={{safeURL QrAuth}}>Open in Statping App</a>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -37,6 +37,7 @@ type Core struct {
|
|||
Version string `gorm:"column:version" json:"version"`
|
||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||
UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
||||
UpdateNotify NullBool `gorm:"column:update_notify;default:false" json:"update_notify,omitempty"`
|
||||
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
// Incident is the main struct for Incidents
|
||||
type Incident struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
Title string `gorm:"column:title" json:"title,omitempty"`
|
||||
Description string `gorm:"column:description" json:"description,omitempty"`
|
||||
ServiceId int64 `gorm:"index;column:service" json:"service"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" json:"updated_at"`
|
||||
Updates []*IncidentUpdate `gorm:"-" json:"updates,omitempty"`
|
||||
}
|
||||
|
||||
// IncidentUpdate contains updates based on a Incident
|
||||
type IncidentUpdate struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
IncidentId int64 `gorm:"index;column:incident" json:"-"`
|
||||
Message string `gorm:"column:message" json:"message,omitempty"`
|
||||
Type string `gorm:"column:type" json:"type,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" json:"updated_at"`
|
||||
}
|
|
@ -34,8 +34,10 @@ type Service struct {
|
|||
Timeout int `gorm:"default:30;column:timeout" json:"timeout"`
|
||||
Order int `gorm:"default:0;column:order_id" json:"order_id"`
|
||||
AllowNotifications NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications"`
|
||||
VerifySSL NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl"`
|
||||
Public NullBool `gorm:"default:true;column:public" json:"public"`
|
||||
GroupId int `gorm:"default:0;column:group_id" json:"group_id"`
|
||||
Headers NullString `gorm:"column:headers" json:"headers"`
|
||||
Permalink NullString `gorm:"column:permalink" json:"permalink"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
|
@ -48,6 +50,10 @@ type Service struct {
|
|||
Checkpoint time.Time `gorm:"-" json:"-"`
|
||||
SleepDuration time.Duration `gorm:"-" json:"-"`
|
||||
LastResponse string `gorm:"-" json:"-"`
|
||||
UserNotified bool `gorm:"-" json:"-"` // True if the User was already notified about a Downtime
|
||||
UpdateNotify bool `gorm:"-" json:"-"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool`
|
||||
DownText string `gorm:"-" json:"-"` // Contains the current generated Downtime Text
|
||||
SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available
|
||||
LastStatusCode int `gorm:"-" json:"status_code"`
|
||||
LastOnline time.Time `gorm:"-" json:"last_success"`
|
||||
Failures []FailureInterface `gorm:"-" json:"failures,omitempty"`
|
||||
|
|
|
@ -20,11 +20,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
TIME_NANO = "2006-01-02T15:04:05Z"
|
||||
TIME = "2006-01-02 15:04:05"
|
||||
POSTGRES_TIME = "2006-01-02 15:04"
|
||||
CHART_TIME = "2006-01-02T15:04:05.999999-07:00"
|
||||
TIME_DAY = "2006-01-02"
|
||||
TIME_NANO = "2006-01-02T15:04:05Z"
|
||||
TIME = "2006-01-02 15:04:05"
|
||||
CHART_TIME = "2006-01-02T15:04:05.999999-07:00"
|
||||
TIME_DAY = "2006-01-02"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -91,6 +91,9 @@ func rotate() {
|
|||
|
||||
// Log creates a new entry in the Logger. Log has 1-5 levels depending on how critical the log/error is
|
||||
func Log(level int, err interface{}) error {
|
||||
if disableLogs {
|
||||
return nil
|
||||
}
|
||||
pushLastLine(err)
|
||||
var outErr error
|
||||
switch level {
|
||||
|
|
188
utils/utils.go
188
utils/utils.go
|
@ -16,15 +16,20 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -33,7 +38,8 @@ import (
|
|||
|
||||
var (
|
||||
// Directory returns the current path or the STATPING_DIR environment variable
|
||||
Directory string
|
||||
Directory string
|
||||
disableLogs bool
|
||||
)
|
||||
|
||||
// init will set the utils.Directory to the current running directory, or STATPING_DIR if it is set
|
||||
|
@ -48,6 +54,8 @@ func init() {
|
|||
}
|
||||
Directory = dir
|
||||
}
|
||||
logger := os.Getenv("DISABLE_LOGS")
|
||||
disableLogs, _ = strconv.ParseBool(logger)
|
||||
}
|
||||
|
||||
// ToInt converts a int to a string
|
||||
|
@ -78,6 +86,25 @@ func ToInt(s interface{}) int64 {
|
|||
}
|
||||
}
|
||||
|
||||
// ConvertInterface will take all the keys/values from an interface and replace all %type.Key from a string
|
||||
// Input: {"name": "%service.Name", "domain": "%service.Domain"}
|
||||
// Output: {"name": "Google DNS", "domain": "8.8.8.8"}
|
||||
func ConvertInterface(in string, obj interface{}) string {
|
||||
if reflect.ValueOf(obj).IsNil() {
|
||||
return in
|
||||
}
|
||||
s := reflect.ValueOf(obj).Elem()
|
||||
typeOfT := s.Type()
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := s.Field(i)
|
||||
find := strings.Split(fmt.Sprintf("%s.%v", typeOfT, typeOfT.Field(i).Name), ".")
|
||||
find[1] = strings.ToLower(find[1])
|
||||
key := strings.Join(find[1:], ".")
|
||||
in = strings.ReplaceAll(in, fmt.Sprintf("%%%v", key), fmt.Sprintf("%v", f.Interface()))
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// ToString converts a int to a string
|
||||
func ToString(s interface{}) string {
|
||||
switch v := s.(type) {
|
||||
|
@ -249,21 +276,8 @@ func SaveFile(filename string, data []byte) error {
|
|||
// // body - The body or form data to send with HTTP request
|
||||
// // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds)
|
||||
// // You can use a HTTP Proxy if you HTTP_PROXY environment variable
|
||||
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration) ([]byte, *http.Response, error) {
|
||||
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool) ([]byte, *http.Response, error) {
|
||||
var err error
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
DisableKeepAlives: true,
|
||||
ResponseHeaderTimeout: timeout,
|
||||
TLSHandshakeTimeout: timeout,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: timeout,
|
||||
}
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequest(method, url, body); err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -276,11 +290,41 @@ func HttpRequest(url, method string, content interface{}, headers []string, body
|
|||
keyVal := strings.Split(h, "=")
|
||||
if len(keyVal) == 2 {
|
||||
if keyVal[0] != "" && keyVal[1] != "" {
|
||||
req.Header.Set(keyVal[0], keyVal[1])
|
||||
if strings.ToLower(keyVal[0]) == "host" {
|
||||
req.Host = strings.TrimSpace(keyVal[1])
|
||||
} else {
|
||||
req.Header.Set(keyVal[0], keyVal[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var resp *http.Response
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout,
|
||||
KeepAlive: timeout,
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: !verifySSL,
|
||||
ServerName: req.Host,
|
||||
},
|
||||
DisableKeepAlives: true,
|
||||
ResponseHeaderTimeout: timeout,
|
||||
TLSHandshakeTimeout: timeout,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// redirect all connections to host specified in url
|
||||
addr = strings.Split(req.URL.Host, ":")[0] + addr[strings.LastIndex(addr, ":"):]
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
},
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
if resp, err = client.Do(req); err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
@ -288,3 +332,115 @@ func HttpRequest(url, method string, content interface{}, headers []string, body
|
|||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
return contents, resp, err
|
||||
}
|
||||
|
||||
const (
|
||||
B = 0x100
|
||||
N = 0x1000
|
||||
BM = 0xff
|
||||
)
|
||||
|
||||
func NewPerlin(alpha, beta float64, n int, seed int64) *Perlin {
|
||||
return NewPerlinRandSource(alpha, beta, n, rand.NewSource(seed))
|
||||
}
|
||||
|
||||
// Perlin is the noise generator
|
||||
type Perlin struct {
|
||||
alpha float64
|
||||
beta float64
|
||||
n int
|
||||
|
||||
p [B + B + 2]int
|
||||
g3 [B + B + 2][3]float64
|
||||
g2 [B + B + 2][2]float64
|
||||
g1 [B + B + 2]float64
|
||||
}
|
||||
|
||||
func NewPerlinRandSource(alpha, beta float64, n int, source rand.Source) *Perlin {
|
||||
var p Perlin
|
||||
var i int
|
||||
|
||||
p.alpha = alpha
|
||||
p.beta = beta
|
||||
p.n = n
|
||||
|
||||
r := rand.New(source)
|
||||
|
||||
for i = 0; i < B; i++ {
|
||||
p.p[i] = i
|
||||
p.g1[i] = float64((r.Int()%(B+B))-B) / B
|
||||
|
||||
for j := 0; j < 2; j++ {
|
||||
p.g2[i][j] = float64((r.Int()%(B+B))-B) / B
|
||||
}
|
||||
|
||||
normalize2(&p.g2[i])
|
||||
}
|
||||
|
||||
for ; i > 0; i-- {
|
||||
k := p.p[i]
|
||||
j := r.Int() % B
|
||||
p.p[i] = p.p[j]
|
||||
p.p[j] = k
|
||||
}
|
||||
|
||||
for i := 0; i < B+2; i++ {
|
||||
p.p[B+i] = p.p[i]
|
||||
p.g1[B+i] = p.g1[i]
|
||||
for j := 0; j < 2; j++ {
|
||||
p.g2[B+i][j] = p.g2[i][j]
|
||||
}
|
||||
for j := 0; j < 3; j++ {
|
||||
p.g3[B+i][j] = p.g3[i][j]
|
||||
}
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func normalize2(v *[2]float64) {
|
||||
s := math.Sqrt(v[0]*v[0] + v[1]*v[1])
|
||||
v[0] = v[0] / s
|
||||
v[1] = v[1] / s
|
||||
}
|
||||
|
||||
func (p *Perlin) Noise1D(x float64) float64 {
|
||||
var scale float64 = 1
|
||||
var sum float64
|
||||
px := x
|
||||
|
||||
for i := 0; i < p.n; i++ {
|
||||
val := p.noise1(px)
|
||||
sum += val / scale
|
||||
scale *= p.alpha
|
||||
px *= p.beta
|
||||
}
|
||||
if sum < 0 {
|
||||
sum = sum * -1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (p *Perlin) noise1(arg float64) float64 {
|
||||
var vec [1]float64
|
||||
vec[0] = arg
|
||||
|
||||
t := vec[0] + N
|
||||
bx0 := int(t) & BM
|
||||
bx1 := (bx0 + 1) & BM
|
||||
rx0 := t - float64(int(t))
|
||||
rx1 := rx0 - 1.
|
||||
|
||||
sx := sCurve(rx0)
|
||||
u := rx0 * p.g1[p.p[bx0]]
|
||||
v := rx1 * p.g1[p.p[bx1]]
|
||||
|
||||
return lerp(sx, u, v)
|
||||
}
|
||||
|
||||
func sCurve(t float64) float64 {
|
||||
return t * t * (3. - 2.*t)
|
||||
}
|
||||
|
||||
func lerp(t, a, b float64) float64 {
|
||||
return a + t*(b-a)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,17 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func TestConvertInterface(t *testing.T) {
|
||||
type Service struct {
|
||||
Name string
|
||||
Domain string
|
||||
}
|
||||
sample := `{"name": "%service.Name", "domain": "%service.Domain"}`
|
||||
input := &Service{"Test Name", "statping.com"}
|
||||
out := ConvertInterface(sample, input)
|
||||
assert.Equal(t, `{"name": "Test Name", "domain": "statping.com"}`, out)
|
||||
}
|
||||
|
||||
func TestCreateLog(t *testing.T) {
|
||||
err := createLog(Directory)
|
||||
assert.Nil(t, err)
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.80.49
|
||||
0.80.64
|
||||
|
|
Loading…
Reference in New Issue