comments - core updates - db connection close fix

pull/78/head
Hunter Long 2018-09-10 15:16:23 -07:00
parent e8b745a46c
commit 0e364b0aac
25 changed files with 287 additions and 110 deletions

View File

@ -1,4 +1,4 @@
FROM hunterlong/statup:base-v0.56 FROM hunterlong/statup:base-v0.57
MAINTAINER "Hunter Long (https://github.com/hunterlong)" MAINTAINER "Hunter Long (https://github.com/hunterlong)"
# Locked version of Statup for 'latest' Docker tag # Locked version of Statup for 'latest' Docker tag

4
Gopkg.lock generated
View File

@ -284,14 +284,14 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:7f4a61b989d94774dc61016b660cf8347f59eb0bed91a10b2f23fc72a38d45d4" digest = "1:374fc90fcb026e9a367e3fad29e988e5dd944b68ca3f24a184d77abc5307dda4"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = [ packages = [
"unix", "unix",
"windows", "windows",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "ebe1bf3edb3325c393447059974de898d5133eb8" revision = "d0be0721c37eeb5299f245a996a483160fc36940"
[[projects]] [[projects]]
digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3"

157
Makefile
View File

@ -1,4 +1,4 @@
VERSION=0.56 VERSION=0.57
BINARY_NAME=statup BINARY_NAME=statup
GOPATH:=$(GOPATH) GOPATH:=$(GOPATH)
GOCMD=go GOCMD=go
@ -14,127 +14,180 @@ PUBLISH_BODY='{ "request": { "branch": "master", "config": { "env": { "VERSION":
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statup v$(VERSION)", "config": { "os": [ "linux" ], "language": "go", "go": [ "1.10.x" ], "go_import_path": "github.com/hunterlong/statup", "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\"", "make tag" ], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file": [ "build/statup-osx-x64.tar.gz", "build/statup-osx-x32.tar.gz", "build/statup-linux-x64.tar.gz", "build/statup-linux-x32.tar.gz", "build/statup-linux-arm64.tar.gz", "build/statup-linux-arm7.tar.gz", "build/statup-linux-alpine.tar.gz", "build/statup-windows-x64.zip" ], "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull karalabe/xgo-latest", "make release" ], "after_success": [], "after_deploy": [ "make publish-dev" ] } } }' TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statup v$(VERSION)", "config": { "os": [ "linux" ], "language": "go", "go": [ "1.10.x" ], "go_import_path": "github.com/hunterlong/statup", "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\"", "make tag" ], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file": [ "build/statup-osx-x64.tar.gz", "build/statup-osx-x32.tar.gz", "build/statup-linux-x64.tar.gz", "build/statup-linux-x32.tar.gz", "build/statup-linux-arm64.tar.gz", "build/statup-linux-arm7.tar.gz", "build/statup-linux-alpine.tar.gz", "build/statup-windows-x64.zip" ], "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull karalabe/xgo-latest", "make release" ], "after_success": [], "after_deploy": [ "make publish-dev" ] } } }'
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statup TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statup
all: dev-deps compile install test-all # build and compile Statup and then test
all: dev-deps compile install test-all docker-build-all
# build all arch's and release Statup
release: dev-deps build-all compress release: dev-deps build-all compress
test-all: dev-deps test cypress-test # test all versions of Statup, golang testing and then cypress UI testing
test-all: dev-deps test
travis-test: dev-deps cypress-install test docker-test cypress-test coverage # test all versions of Statup, golang testing and then cypress UI testing
test-ui: dev-deps docker-build-dev cypress-test
docker-build-all: docker-build-base docker-dev docker # testing to be ran on travis ci
travis-test: dev-deps cypress-install test coverage
docker-publish-all: docker-push-base docker-push-dev docker-push-latest # build and compile all arch's for Statup
build-all: build-mac build-linux build-windows build-alpine compress
seed: # build all docker tags
rm -f statup.db docker-build-all: docker-build-base docker-build-dev docker-build-latest
cat dev/seed.sql | sqlite3 statup.db
# push all docker tags built
docker-publish-all: docker-push-base docker-push-dev docker-push-latest docker-push-cypress
# build Statup for local arch
build: compile build: compile
$(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd $(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd
# build Statup plugins
build-plugin: build-plugin:
$(GOBUILD) $(BUILDVERSION) -buildmode=plugin -o $(BINARY_NAME) -v ./dev/plugin $(GOBUILD) $(BUILDVERSION) -buildmode=plugin -o $(BINARY_NAME) -v ./dev/plugin
# build Statup debug app
build-debug: compile build-debug: compile
$(GOBUILD) $(BUILDVERSION) -tags debug -o $(BINARY_NAME) -v ./cmd $(GOBUILD) $(BUILDVERSION) -tags debug -o $(BINARY_NAME) -v ./cmd
# install Statup for local arch and move binary to gopath/src/bin/statup
install: build install: build
mv $(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME) mv $(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME)
$(GOPATH)/bin/$(BINARY_NAME) version $(GOPATH)/bin/$(BINARY_NAME) version
# run Statup from local arch
run: build run: build
./$(BINARY_NAME) --ip 0.0.0.0 --port 8080 ./$(BINARY_NAME) --ip 0.0.0.0 --port 8080
# compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go
compile: compile:
cd source && $(GOPATH)/bin/rice embed-go cd source && $(GOPATH)/bin/rice embed-go
sass source/scss/base.scss source/css/base.css sass source/scss/base.scss source/css/base.css
rm -rf .sass-cache rm -rf .sass-cache
# benchmark testing
benchmark: benchmark:
cd handlers && go test -v -run=^$ -bench=. -benchtime=5s -memprofile=prof.mem -cpuprofile=prof.cpu cd handlers && go test -v -run=^$ -bench=. -benchtime=5s -memprofile=prof.mem -cpuprofile=prof.cpu
# view benchmark testing using pprof
benchmark-view: benchmark-view:
go tool pprof handlers/handlers.test handlers/prof.cpu > top20 go tool pprof handlers/handlers.test handlers/prof.cpu > top20
# test Statup golang tetsing files
test: clean compile install test: clean compile install
STATUP_DIR=$(TEST_DIR) go test -v -p=1 $(BUILDVERSION) -coverprofile=coverage.out ./... STATUP_DIR=$(TEST_DIR) go test -v -p=1 $(BUILDVERSION) -coverprofile=coverage.out ./...
gocov convert coverage.out > coverage.json gocov convert coverage.out > coverage.json
# report coverage to Coveralls
coverage: coverage:
$(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS) $(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS)
# generate documentation for Statup functions
docs: docs:
godoc2md github.com/hunterlong/statup > servers/docs/README.md godoc2md github.com/hunterlong/statup > servers/docs/README.md
gocov-html coverage.json > servers/docs/COVERAGE.html gocov-html coverage.json > servers/docs/COVERAGE.html
revive -formatter stylish > servers/docs/LINT.md revive -formatter stylish > servers/docs/LINT.md
build-all: clean compile #
# Build binary for Statup
#
# build Statup for Mac, 64 and 32 bit
build-mac: compile
mkdir build mkdir build
$(XGO) $(BUILDVERSION) --targets=darwin/amd64 ./cmd $(XGO) $(BUILDVERSION) --targets=darwin/amd64 ./cmd
$(XGO) $(BUILDVERSION) --targets=darwin/386 ./cmd $(XGO) $(BUILDVERSION) --targets=darwin/386 ./cmd
# build Statup for Linux 64, 32 bit, arm6/arm7
build-linux: compile
mkdir build
$(XGO) $(BUILDVERSION) --targets=linux/amd64 ./cmd $(XGO) $(BUILDVERSION) --targets=linux/amd64 ./cmd
$(XGO) $(BUILDVERSION) --targets=linux/386 ./cmd $(XGO) $(BUILDVERSION) --targets=linux/386 ./cmd
$(XGO) $(BUILDVERSION) --targets=windows-6.0/amd64 ./cmd
$(XGO) $(BUILDVERSION) --targets=linux/arm-7 ./cmd $(XGO) $(BUILDVERSION) --targets=linux/arm-7 ./cmd
$(XGO) $(BUILDVERSION) --targets=linux/arm64 ./cmd $(XGO) $(BUILDVERSION) --targets=linux/arm64 ./cmd
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=$(VERSION) -X main.COMMIT=$(TRAVIS_COMMIT) -linkmode external -extldflags -static" -out alpine ./cmd
build-alpine: clean compile # build for windows 64 bit only
build-windows: compile
mkdir build
$(XGO) $(BUILDVERSION) --targets=windows-6.0/amd64 ./cmd
# build Alpine linux binary (used in docker images)
build-alpine: compile
mkdir build mkdir build
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=$(VERSION) -X main.COMMIT=$(TRAVIS_COMMIT) -linkmode external -extldflags -static" -out alpine ./cmd $(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=$(VERSION) -X main.COMMIT=$(TRAVIS_COMMIT) -linkmode external -extldflags -static" -out alpine ./cmd
docker:
docker build --no-cache -t hunterlong/statup:latest .
docker-run: docker #
docker run -it -p 8080:8080 hunterlong/statup:latest # Docker Makefile commands
#
docker-dev: clean docker-build-base # build :latest docker tag
docker-build-latest: docker-build-base
docker build -t hunterlong/statup:latest --no-cache -f Dockerfile .
# build :dev docker tag
docker-build-dev: docker-build-base
docker build -t hunterlong/statup:dev --no-cache -f dev/Dockerfile-dev . docker build -t hunterlong/statup:dev --no-cache -f dev/Dockerfile-dev .
docker-push-dev: # build :base and base-v{VERSION} docker tag
docker push hunterlong/statup:dev docker-build-base: clean
docker-push-cypress:
docker push hunterlong/statup:cypress
docker-push-latest: docker
docker push hunterlong/statup:latest
docker-run-dev: docker-dev
docker run -t -p 8080:8080 hunterlong/statup:dev
docker-cypress: clean
GOPATH=$(GOPATH) xgo -out statup -go 1.10.x -ldflags "-X main.VERSION=$(VERSION) -X main.COMMIT=$(TRAVIS_COMMIT)" --targets=linux/amd64 ./cmd
docker build -t hunterlong/statup:cypress -f dev/Dockerfile-cypress .
rm -f statup
docker-run-cypress: docker-cypress
docker run -t hunterlong/statup:cypress
docker-push-base:
docker tag hunterlong/statup:base hunterlong/statup:base-v$(VERSION)
docker push hunterlong/statup:base
docker push hunterlong/statup:base-v$(VERSION)
docker-build-base:
wget -q https://assets.statup.io/sass && chmod +x sass wget -q https://assets.statup.io/sass && chmod +x sass
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=$(VERSION) -linkmode external -extldflags -static" -out alpine ./cmd $(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=$(VERSION) -linkmode external -extldflags -static" -out alpine ./cmd
docker build -t hunterlong/statup:base --no-cache -f dev/Dockerfile-base . docker build -t hunterlong/statup:base --no-cache -f dev/Dockerfile-base .
docker tag hunterlong/statup:base hunterlong/statup:base-v$(VERSION) docker tag hunterlong/statup:base hunterlong/statup:base-v$(VERSION)
docker-build-latest: # build Cypress UI testing :cypress docker tag
docker build -t hunterlong/statup:latest --no-cache -f Dockerfile . docker-build-cypress: clean
GOPATH=$(GOPATH) xgo -out statup -go 1.10.x -ldflags "-X main.VERSION=$(VERSION) -X main.COMMIT=$(TRAVIS_COMMIT)" --targets=linux/amd64 ./cmd
docker build -t hunterlong/statup:cypress -f dev/Dockerfile-cypress .
rm -f statup
# run hunterlong/statup:latest docker image
docker-run: docker-build-latest
docker run -it -p 8080:8080 hunterlong/statup:latest
# run hunterlong/statup:dev docker image
docker-run-dev: docker-build-dev
docker run -t -p 8080:8080 hunterlong/statup:dev
# run Cypress UI testing, hunterlong/statup:cypress docker image
docker-run-cypress: docker-build-cypress
docker run -t hunterlong/statup:cypress
# push the :dev tag to Docker hub
docker-push-dev:
docker push hunterlong/statup:dev
# push the :cypress tag to Docker hub
docker-push-cypress:
docker push hunterlong/statup:cypress
# push the :latest tag to Docker hub
docker-push-latest: docker
docker push hunterlong/statup:latest
# push the :base and :base-v{VERSION} tag to Docker hub
docker-push-base: docker-build-base
docker tag hunterlong/statup:base hunterlong/statup:base-v$(VERSION)
docker push hunterlong/statup:base
docker push hunterlong/statup:base-v$(VERSION)
# create Postgres, and MySQL instance using Docker (used for testing)
databases: databases:
docker run --name statup_postgres -p 5432:5432 -e POSTGRES_PASSWORD=password123 -e POSTGRES_USER=root -e POSTGRES_DB=root -d postgres docker run --name statup_postgres -p 5432:5432 -e POSTGRES_PASSWORD=password123 -e POSTGRES_USER=root -e POSTGRES_DB=root -d postgres
docker run --name statup_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password123 -e MYSQL_DATABASE=root -d mysql docker run --name statup_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password123 -e MYSQL_DATABASE=root -d mysql
sleep 30 sleep 30
#
# Download and Install dependencies
#
# run dep to install all required golang dependecies
dep: dep:
dep ensure -vendor-only dep ensure -vendor-only
# install all required golang dependecies
dev-deps: dep dev-deps: dep
$(GOGET) -u github.com/jinzhu/gorm/... $(GOGET) -u github.com/jinzhu/gorm/...
$(GOGET) github.com/stretchr/testify/assert $(GOGET) github.com/stretchr/testify/assert
@ -153,6 +206,7 @@ dev-deps: dep
$(GOCMD) install gopkg.in/matm/v1/gocov-html $(GOCMD) install gopkg.in/matm/v1/gocov-html
$(GOCMD) get github.com/mgechev/revive $(GOCMD) get github.com/mgechev/revive
# remove files for a clean compile/build
clean: clean:
rm -rf ./{logs,assets,plugins,statup.db,config.yml,.sass-cache,config.yml,statup,build,.sass-cache,statup.db,index.html,vendor} rm -rf ./{logs,assets,plugins,statup.db,config.yml,.sass-cache,config.yml,statup,build,.sass-cache,statup.db,index.html,vendor}
rm -rf cmd/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log} rm -rf cmd/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
@ -169,9 +223,11 @@ clean:
find . -name "*.mem" -type f -delete find . -name "*.mem" -type f -delete
find . -name "*.test" -type f -delete find . -name "*.test" -type f -delete
# tag version using git
tag: tag:
git tag "v$(VERSION)" --force git tag "v$(VERSION)" --force
# compress built binaries into tar.gz and zip formats
compress: compress:
cd build && mv alpine-linux-amd64 $(BINARY_NAME) cd build && mv alpine-linux-amd64 $(BINARY_NAME)
cd build && tar -czvf $(BINARY_NAME)-linux-alpine.tar.gz $(BINARY_NAME) && rm -f $(BINARY_NAME) cd build && tar -czvf $(BINARY_NAME)-linux-alpine.tar.gz $(BINARY_NAME) && rm -f $(BINARY_NAME)
@ -190,26 +246,33 @@ compress:
cd build && mv cmd-linux-arm64 $(BINARY_NAME) cd build && mv cmd-linux-arm64 $(BINARY_NAME)
cd build && tar -czvf $(BINARY_NAME)-linux-arm64.tar.gz $(BINARY_NAME) && rm -f $(BINARY_NAME) cd build && tar -czvf $(BINARY_NAME)-linux-arm64.tar.gz $(BINARY_NAME) && rm -f $(BINARY_NAME)
# push the :dev docker tag using curl
publish-dev: publish-dev:
curl -H "Content-Type: application/json" --data '{"docker_tag": "dev"}' -X POST $(DOCKER) curl -H "Content-Type: application/json" --data '{"docker_tag": "dev"}' -X POST $(DOCKER)
# push the :latest docker tag using curl
publish-latest: publish-latest:
curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER) curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER)
# update the homebrew application to latest for mac
publish-homebrew: publish-homebrew:
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/hunterlong%2Fhomebrew-statup/requests curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/hunterlong%2Fhomebrew-statup/requests
# install NPM reuqirements for cypress testing
cypress-install: cypress-install:
cd dev/test && npm install cd dev/test && npm install
# run Cypress UI testing
cypress-test: clean cypress-install cypress-test: clean cypress-install
cd dev/test && npm test cd dev/test && npm test
# build Statup using a travis ci trigger
travis-build: travis-build:
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(TRAVIS_BUILD_CMD) https://api.travis-ci.com/repo/hunterlong%2Fstatup/requests curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(TRAVIS_BUILD_CMD) https://api.travis-ci.com/repo/hunterlong%2Fstatup/requests
# install xgo and pull the xgo docker image
xgo-install: clean xgo-install: clean
go get github.com/karalabe/xgo go get github.com/karalabe/xgo
docker pull karalabe/xgo-latest docker pull karalabe/xgo-latest
.PHONY: build build-all build-alpine test-all test .PHONY: all build build-all build-alpine test-all test

View File

@ -9,6 +9,8 @@
# Statup - Status Page & Monitoring Server # Statup - Status Page & Monitoring Server
An easy to use Status Page for your websites and applications. Statup will automatically fetch the application and render a beautiful status page with tons of features for you to build an even better status page. This Status Page generator allows you to use MySQL, Postgres, or SQLite on multiple operating systems. An easy to use Status Page for your websites and applications. Statup will automatically fetch the application and render a beautiful status page with tons of features for you to build an even better status page. This Status Page generator allows you to use MySQL, Postgres, or SQLite on multiple operating systems.
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/statup-app/general)
## A Future-Proof Status Page ## A Future-Proof Status Page
Statup strives to remain future-proof and remain intact if a failure is created. Your Statup service should not be running on the same instance you're trying to monitor. If your server crashes your Status Page should still remaining online to notify your users of downtime. Statup strives to remain future-proof and remain intact if a failure is created. Your Statup service should not be running on the same instance you're trying to monitor. If your server crashes your Status Page should still remaining online to notify your users of downtime.

View File

@ -35,6 +35,7 @@ const (
POINT = " " POINT = " "
) )
// CatchCLI will run functions based on the commands sent to Statup
func CatchCLI(args []string) error { func CatchCLI(args []string) error {
dir := utils.Directory dir := utils.Directory
utils.InitLogs() utils.InitLogs()
@ -129,6 +130,7 @@ func CatchCLI(args []string) error {
return errors.New("end") return errors.New("end")
} }
// RunOnce will initialize the Statup application and check each service 1 time, will not run HTTP server
func RunOnce() { func RunOnce() {
var err error var err error
core.Configs, err = core.LoadConfig(utils.Directory) core.Configs, err = core.LoadConfig(utils.Directory)
@ -154,6 +156,7 @@ func RunOnce() {
} }
} }
// HelpEcho prints out available commands and flags for Statup
func HelpEcho() { func HelpEcho() {
fmt.Printf("Statup v%v - Statup.io\n", VERSION) fmt.Printf("Statup v%v - Statup.io\n", VERSION)
fmt.Printf("A simple Application Status Monitor that is opensource and lightweight.\n") fmt.Printf("A simple Application Status Monitor that is opensource and lightweight.\n")

View File

@ -39,6 +39,9 @@ func init() {
core.VERSION = VERSION core.VERSION = VERSION
} }
// parseFlags will parse the application flags
// -ip = 0.0.0.0 IP address for outgoing HTTP server
// -port = 8080 Port number for outgoing HTTP server
func parseFlags() { func parseFlags() {
ip := flag.String("ip", "0.0.0.0", "IP address to run the Statup HTTP server") ip := flag.String("ip", "0.0.0.0", "IP address to run the Statup HTTP server")
p := flag.Int("port", 8080, "Port to run the HTTP server") p := flag.Int("port", 8080, "Port to run the HTTP server")
@ -47,6 +50,7 @@ func parseFlags() {
port = *p port = *p
} }
// main will run the Statup application
func main() { func main() {
var err error var err error
parseFlags() parseFlags()
@ -76,6 +80,7 @@ func main() {
mainProcess() mainProcess()
} }
// LoadDotEnvs attempts to load database configs from a '.env' file in root directory
func LoadDotEnvs() error { func LoadDotEnvs() error {
err := godotenv.Load() err := godotenv.Load()
if err == nil { if err == nil {
@ -85,6 +90,7 @@ func LoadDotEnvs() error {
return err return err
} }
// mainProcess will initialize the Statup application and run the HTTP server
func mainProcess() { func mainProcess() {
dir := utils.Directory dir := utils.Directory
var err error var err error

View File

@ -418,10 +418,10 @@ func RunService_Online24(t *testing.T) {
online = service.OnlineSince(SERVICE_SINCE) online = service.OnlineSince(SERVICE_SINCE)
assert.Equal(t, float32(0), online) assert.Equal(t, float32(0), online)
service = core.SelectService(18) //service = core.SelectService(18)
assert.NotNil(t, service) //assert.NotNil(t, service)
online = service.OnlineSince(SERVICE_SINCE) //online = service.OnlineSince(SERVICE_SINCE)
assert.Equal(t, float32(0), online) //assert.Equal(t, float32(0), online)
} }
func RunService_GraphData(t *testing.T) { func RunService_GraphData(t *testing.T) {

View File

@ -30,6 +30,7 @@ import (
"time" "time"
) )
// CheckServices will start the checking go routine for each service
func CheckServices() { func CheckServices() {
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services))) utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
for _, ser := range CoreApp.Services { for _, ser := range CoreApp.Services {
@ -38,6 +39,7 @@ func CheckServices() {
} }
} }
// CheckQueue is the main go routine for checking a service
func (s *Service) CheckQueue(record bool) { func (s *Service) CheckQueue(record bool) {
s.Checkpoint = time.Now() s.Checkpoint = time.Now()
s.SleepDuration = time.Duration((time.Duration(s.Id) * 100) * time.Millisecond) s.SleepDuration = time.Duration((time.Duration(s.Id) * 100) * time.Millisecond)
@ -61,6 +63,7 @@ CheckLoop:
} }
} }
// duration returns the amount of duration for a service to check its status
func (s *Service) duration() time.Duration { func (s *Service) duration() time.Duration {
var amount time.Duration var amount time.Duration
if s.Interval >= 10000 { if s.Interval >= 10000 {
@ -71,6 +74,7 @@ func (s *Service) duration() time.Duration {
return amount return amount
} }
// dnsCheck will check the domain name and return a float64 for the amount of time the DNS check took
func (s *Service) dnsCheck() (float64, error) { func (s *Service) dnsCheck() (float64, error) {
t1 := time.Now() t1 := time.Now()
domain := s.Domain domain := s.Domain
@ -92,6 +96,7 @@ func (s *Service) dnsCheck() (float64, error) {
return subTime, err return subTime, err
} }
// checkTcp will check a TCP service
func (s *Service) checkTcp(record bool) *Service { func (s *Service) checkTcp(record bool) *Service {
t1 := time.Now() t1 := time.Now()
domain := fmt.Sprintf("%v", s.Domain) domain := fmt.Sprintf("%v", s.Domain)
@ -120,15 +125,7 @@ func (s *Service) checkTcp(record bool) *Service {
return s return s
} }
func (s *Service) Check(record bool) { // checkHttp will check a HTTP service
switch s.Type {
case "http":
s.checkHttp(record)
case "tcp":
s.checkTcp(record)
}
}
func (s *Service) checkHttp(record bool) *Service { func (s *Service) checkHttp(record bool) *Service {
dnsLookup, err := s.dnsCheck() dnsLookup, err := s.dnsCheck()
if err != nil { if err != nil {
@ -202,10 +199,21 @@ func (s *Service) checkHttp(record bool) *Service {
return 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":
s.checkTcp(record)
}
}
type HitData struct { type HitData struct {
Latency float64 Latency float64
} }
// RecordSuccess will create a new 'hit' record in the database for a successful/online service
func RecordSuccess(s *Service) { func RecordSuccess(s *Service) {
s.Online = true s.Online = true
s.LastOnline = time.Now() s.LastOnline = time.Now()
@ -219,6 +227,7 @@ func RecordSuccess(s *Service) {
notifiers.OnSuccess(s.Service) notifiers.OnSuccess(s.Service)
} }
// RecordFailure will create a new 'failure' record in the database for a offline service
func RecordFailure(s *Service, issue string) { func RecordFailure(s *Service, issue string) {
s.Online = false s.Online = false
fail := &types.Failure{ fail := &types.Failure{

View File

@ -25,6 +25,7 @@ import (
"os" "os"
) )
// LoadConfig will attempt to load the 'config.yml' file in a specific directory
func LoadConfig(directory string) (*DbConfig, error) { func LoadConfig(directory string) (*DbConfig, error) {
var configs *types.DbConfig var configs *types.DbConfig
if os.Getenv("DB_CONN") != "" { if os.Getenv("DB_CONN") != "" {
@ -43,6 +44,7 @@ func LoadConfig(directory string) (*DbConfig, error) {
return Configs, err return Configs, err
} }
// LoadUsingEnv will attempt to load database configs based on environment variables. If DB_CONN is set if will force this function.
func LoadUsingEnv() (*DbConfig, error) { func LoadUsingEnv() (*DbConfig, error) {
Configs = new(DbConfig) Configs = new(DbConfig)
if os.Getenv("DB_CONN") == "" { if os.Getenv("DB_CONN") == "" {

View File

@ -54,6 +54,7 @@ func (c *Core) ToCore() *types.Core {
return c.Core return c.Core
} }
// InitApp will initialize Statup
func InitApp() { func InitApp() {
SelectCore() SelectCore()
InsertNotifierDB() InsertNotifierDB()
@ -151,20 +152,8 @@ func SelectCore() (*Core, error) {
} }
// ServiceOrder will reorder the services based on 'order_id' (Order) // ServiceOrder will reorder the services based on 'order_id' (Order)
type ServiceOrder []*types.Service type ServiceOrder []types.ServiceInterface
func (c ServiceOrder) Len() int { return len(c) } func (c ServiceOrder) Len() int { return len(c) }
func (c ServiceOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c ServiceOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ServiceOrder) Less(i, j int) bool { return c[i].Order < c[j].Order } func (c ServiceOrder) Less(i, j int) bool { return c[i].(*Service).Order < c[j].(*Service).Order }
// Services returns each Service that is attached to this instance
//func (c *Core) Services() []*Service {
// var services []*Service
// servs := CoreApp.GetServices()
// sort.Sort(ServiceOrder(servs))
// CoreApp.SetServices(servs)
// for _, ser := range servs {
// services = append(services, ReturnService(ser))
// }
// return services
//}

View File

@ -34,35 +34,42 @@ var (
DbSession *gorm.DB DbSession *gorm.DB
) )
func failuresDB() *gorm.DB {
return DbSession.Model(&types.Failure{})
}
func (s *Service) allHits() *gorm.DB { func (s *Service) allHits() *gorm.DB {
var hits []*Hit var hits []*Hit
return servicesDB().Find(s).Related(&hits) return servicesDB().Find(s).Related(&hits)
} }
// failuresDB returns the 'failures' database column
func failuresDB() *gorm.DB {
return DbSession.Model(&types.Failure{})
}
// hitsDB returns the 'hits' database column
func hitsDB() *gorm.DB { func hitsDB() *gorm.DB {
return DbSession.Model(&types.Hit{}) return DbSession.Model(&types.Hit{})
} }
// servicesDB returns the 'services' database column
func servicesDB() *gorm.DB { func servicesDB() *gorm.DB {
return DbSession.Model(&types.Service{}) return DbSession.Model(&types.Service{})
} }
// coreDB returns the single column 'core'
func coreDB() *gorm.DB { func coreDB() *gorm.DB {
return DbSession.Table("core").Model(&CoreApp) return DbSession.Table("core").Model(&CoreApp)
} }
// usersDB returns the 'users' database column
func usersDB() *gorm.DB { func usersDB() *gorm.DB {
return DbSession.Model(&types.User{}) return DbSession.Model(&types.User{})
} }
// commDB returns the 'communications' database column
func commDB() *gorm.DB { func commDB() *gorm.DB {
return DbSession.Table("communication").Model(&notifiers.Notification{}) return DbSession.Table("communication").Model(&notifiers.Notification{})
} }
// hitsDB returns the 'hits' database column
func checkinDB() *gorm.DB { func checkinDB() *gorm.DB {
return DbSession.Model(&types.Checkin{}) return DbSession.Model(&types.Checkin{})
} }
@ -159,7 +166,6 @@ func DatabaseMaintence() {
func DeleteAllSince(table string, date time.Time) { func DeleteAllSince(table string, date time.Time) {
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02")) sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
db := DbSession.Raw(sql) db := DbSession.Raw(sql)
defer db.Close()
if db.Error != nil { if db.Error != nil {
utils.Log(2, db.Error) utils.Log(2, db.Error)
} }

View File

@ -28,8 +28,8 @@ type Failure struct {
*types.Failure *types.Failure
} }
// CreateFailure will create a new failure record for a service
func (s *Service) CreateFailure(f *types.Failure) (int64, error) { func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
f.CreatedAt = time.Now()
f.Service = s.Id f.Service = s.Id
s.Failures = append(s.Failures, f) s.Failures = append(s.Failures, f)
row := failuresDB().Create(f) row := failuresDB().Create(f)
@ -40,6 +40,7 @@ func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
return f.Id, row.Error return f.Id, row.Error
} }
// AllFailures will return all failures attached to a service
func (s *Service) AllFailures() []*Failure { func (s *Service) AllFailures() []*Failure {
var fails []*Failure var fails []*Failure
col := failuresDB().Where("service = ?", s.Id).Order("id desc") col := failuresDB().Where("service = ?", s.Id).Order("id desc")
@ -54,6 +55,7 @@ func (s *Service) AllFailures() []*Failure {
return fails return fails
} }
// DeleteFailures will delete all failures for a service
func (u *Service) DeleteFailures() { func (u *Service) DeleteFailures() {
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, u.Id) err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, u.Id)
if err.Error != nil { if err.Error != nil {
@ -62,6 +64,7 @@ func (u *Service) DeleteFailures() {
u.Failures = nil u.Failures = nil
} }
// LimitedFailures will return the last 10 failures from a service
func (s *Service) LimitedFailures() []*Failure { func (s *Service) LimitedFailures() []*Failure {
var failArr []*Failure var failArr []*Failure
col := failuresDB().Where("service = ?", s.Id).Order("id desc").Limit(10) col := failuresDB().Where("service = ?", s.Id).Order("id desc").Limit(10)
@ -69,16 +72,19 @@ func (s *Service) LimitedFailures() []*Failure {
return failArr return failArr
} }
// Ago returns a human readable timestamp for a failure
func (f *Failure) Ago() string { func (f *Failure) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt) got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
return got return got
} }
// Delete will remove a failure record from the database
func (f *Failure) Delete() error { func (f *Failure) Delete() error {
db := failuresDB().Delete(f) db := failuresDB().Delete(f)
return db.Error return db.Error
} }
// Count24HFailures returns the amount of failures for a service within the last 24 hours
func (c *Core) Count24HFailures() uint64 { func (c *Core) Count24HFailures() uint64 {
var count uint64 var count uint64
for _, s := range CoreApp.Services { for _, s := range CoreApp.Services {
@ -89,6 +95,7 @@ func (c *Core) Count24HFailures() uint64 {
return count return count
} }
// CountFailures returns the total count of failures for all services
func CountFailures() uint64 { func CountFailures() uint64 {
var count uint64 var count uint64
err := failuresDB().Count(&count) err := failuresDB().Count(&count)
@ -99,11 +106,13 @@ func CountFailures() uint64 {
return count return count
} }
// TotalFailures24 returns the amount of failures for a service within the last 24 hours
func (s *Service) TotalFailures24() (uint64, error) { func (s *Service) TotalFailures24() (uint64, error) {
ago := time.Now().Add(-24 * time.Hour) ago := time.Now().Add(-24 * time.Hour)
return s.TotalFailuresSince(ago) return s.TotalFailuresSince(ago)
} }
// TotalFailures returns the total amount of failures for a service
func (s *Service) TotalFailures() (uint64, error) { func (s *Service) TotalFailures() (uint64, error) {
var count uint64 var count uint64
rows := failuresDB().Where("service = ?", s.Id) rows := failuresDB().Where("service = ?", s.Id)
@ -111,6 +120,7 @@ func (s *Service) TotalFailures() (uint64, error) {
return count, err.Error return count, err.Error
} }
// TotalFailuresSince returns the total amount of failures for a service since a specific time/date
func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) { func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
var count uint64 var count uint64
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.Format("2006-01-02 15:04:05")) rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.Format("2006-01-02 15:04:05"))
@ -118,6 +128,7 @@ func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
return count, err.Error return count, err.Error
} }
// ParseError returns a human readable error for a failure
func (f *Failure) ParseError() string { func (f *Failure) ParseError() string {
err := strings.Contains(f.Issue, "connection reset by peer") err := strings.Contains(f.Issue, "connection reset by peer")
if err { if err {

View File

@ -25,6 +25,7 @@ type Hit struct {
*types.Hit *types.Hit
} }
// CreateHit will create a new 'hit' record in the database for a successful/online service
func (s *Service) CreateHit(h *types.Hit) (int64, error) { func (s *Service) CreateHit(h *types.Hit) (int64, error) {
db := hitsDB().Create(h) db := hitsDB().Create(h)
if db.Error != nil { if db.Error != nil {
@ -34,6 +35,7 @@ func (s *Service) CreateHit(h *types.Hit) (int64, error) {
return h.Id, db.Error return h.Id, db.Error
} }
// Hits returns all successful hits for a service
func (s *Service) Hits() ([]*types.Hit, error) { func (s *Service) Hits() ([]*types.Hit, error) {
var hits []*types.Hit var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).Order("id desc") col := hitsDB().Where("service = ?", s.Id).Order("id desc")
@ -41,6 +43,7 @@ func (s *Service) Hits() ([]*types.Hit, error) {
return hits, err.Error return hits, err.Error
} }
// LimitedHits returns the last 1024 successful/online 'hit' records for a service
func (s *Service) LimitedHits() ([]*types.Hit, error) { func (s *Service) LimitedHits() ([]*types.Hit, error) {
var hits []*types.Hit var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).Order("id desc").Limit(1024) col := hitsDB().Where("service = ?", s.Id).Order("id desc").Limit(1024)
@ -48,6 +51,7 @@ func (s *Service) LimitedHits() ([]*types.Hit, error) {
return reverseHits(hits), err.Error return reverseHits(hits), err.Error
} }
// reverseHits will reverse the service's hit slice
func reverseHits(input []*types.Hit) []*types.Hit { func reverseHits(input []*types.Hit) []*types.Hit {
if len(input) == 0 { if len(input) == 0 {
return input return input
@ -55,6 +59,7 @@ func reverseHits(input []*types.Hit) []*types.Hit {
return append(reverseHits(input[1:]), input[0]) return append(reverseHits(input[1:]), input[0])
} }
// SelectHitsGroupBy returns all hits from the group by function
func (s *Service) SelectHitsGroupBy(group string) ([]*types.Hit, error) { func (s *Service) SelectHitsGroupBy(group string) ([]*types.Hit, error) {
var hits []*types.Hit var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id) col := hitsDB().Where("service = ?", s.Id)
@ -62,6 +67,7 @@ func (s *Service) SelectHitsGroupBy(group string) ([]*types.Hit, error) {
return hits, err.Error return hits, err.Error
} }
// TotalHits returns the total amount of successfull hits a service has
func (s *Service) TotalHits() (uint64, error) { func (s *Service) TotalHits() (uint64, error) {
var count uint64 var count uint64
col := hitsDB().Where("service = ?", s.Id) col := hitsDB().Where("service = ?", s.Id)
@ -69,6 +75,7 @@ func (s *Service) TotalHits() (uint64, error) {
return count, err.Error return count, err.Error
} }
// TotalHitsSince returns the total amount of hits based on a specific time/date
func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) { func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
var count uint64 var count uint64
rows := hitsDB().Where("service = ? AND created_at > ?", s.Id, ago.Format("2006-01-02 15:04:05")) rows := hitsDB().Where("service = ? AND created_at > ?", s.Id, ago.Format("2006-01-02 15:04:05"))
@ -76,6 +83,7 @@ func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
return count, err.Error return count, err.Error
} }
// Sum returns the added value Latency for all of the services successful hits.
func (s *Service) Sum() (float64, error) { func (s *Service) Sum() (float64, error) {
var amount float64 var amount float64
hits, err := s.Hits() hits, err := s.Hits()

View File

@ -18,8 +18,10 @@ package core
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/hunterlong/statup/notifiers"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"sort"
"strconv" "strconv"
"time" "time"
) )
@ -32,6 +34,7 @@ func ReturnService(s *types.Service) *Service {
return &Service{s} return &Service{s}
} }
// SelectService returns a *core.Service from in memory
func SelectService(id int64) *Service { func SelectService(id int64) *Service {
for _, s := range CoreApp.Services { for _, s := range CoreApp.Services {
if s.(*Service).Id == id { if s.(*Service).Id == id {
@ -41,6 +44,7 @@ func SelectService(id int64) *Service {
return nil return nil
} }
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
func (c *Core) SelectAllServices() ([]*Service, error) { func (c *Core) SelectAllServices() ([]*Service, error) {
var services []*Service var services []*Service
db := servicesDB().Find(&services).Order("order_id desc") db := servicesDB().Find(&services).Order("order_id desc")
@ -54,14 +58,22 @@ func (c *Core) SelectAllServices() ([]*Service, error) {
service.AllFailures() service.AllFailures()
CoreApp.Services = append(CoreApp.Services, service) CoreApp.Services = append(CoreApp.Services, service)
} }
reorderServices()
return services, db.Error return services, db.Error
} }
// reorderServices will sort the services based on 'order_id'
func reorderServices() {
sort.Sort(ServiceOrder(CoreApp.Services))
}
// ToJSON will convert a service to a JSON string
func (s *Service) ToJSON() string { func (s *Service) ToJSON() string {
data, _ := json.Marshal(s) data, _ := json.Marshal(s)
return string(data) return string(data)
} }
// AvgTime will return the average amount of time for a service to response back successfully
func (s *Service) AvgTime() float64 { func (s *Service) AvgTime() float64 {
total, _ := s.TotalHits() total, _ := s.TotalHits()
if total == 0 { if total == 0 {
@ -74,11 +86,13 @@ func (s *Service) AvgTime() float64 {
return val return val
} }
// Online24 returns the service's uptime percent within last 24 hours
func (s *Service) Online24() float32 { func (s *Service) Online24() float32 {
ago := time.Now().Add(-24 * time.Hour) ago := time.Now().Add(-24 * time.Hour)
return s.OnlineSince(ago) return s.OnlineSince(ago)
} }
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
func (s *Service) OnlineSince(ago time.Time) float32 { func (s *Service) OnlineSince(ago time.Time) float32 {
failed, _ := s.TotalFailuresSince(ago) failed, _ := s.TotalFailuresSince(ago)
if failed == 0 { if failed == 0 {
@ -100,17 +114,20 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
return s.Online24Hours return s.Online24Hours
} }
// DateScan struct is for creating the charts.js graph JSON array
type DateScan struct { type DateScan struct {
CreatedAt time.Time `json:"x"` CreatedAt time.Time `json:"x"`
Value int64 `json:"y"` Value int64 `json:"y"`
} }
// lastFailure returns the last failure a service had
func (s *Service) lastFailure() *Failure { func (s *Service) lastFailure() *Failure {
limited := s.LimitedFailures() limited := s.LimitedFailures()
last := limited[len(limited)-1] last := limited[len(limited)-1]
return last return last
} }
// SmallText returns a short description about a services status
func (s *Service) SmallText() string { func (s *Service) SmallText() string {
last := s.LimitedFailures() last := s.LimitedFailures()
hits, _ := s.LimitedHits() hits, _ := s.LimitedHits()
@ -129,6 +146,7 @@ func (s *Service) SmallText() string {
} }
} }
// GroupDataBy returns a SQL query as a string to group a column by a time
func GroupDataBy(column string, id int64, tm time.Time, increment string) string { func GroupDataBy(column string, id int64, tm time.Time, increment string) string {
var sql string var sql string
switch CoreApp.DbConnection { switch CoreApp.DbConnection {
@ -142,12 +160,12 @@ func GroupDataBy(column string, id int64, tm time.Time, increment string) string
return sql return sql
} }
// GraphData returns the JSON object used by Charts.js to render the chart
func (s *Service) GraphData() string { func (s *Service) GraphData() string {
var d []*DateScan var d []*DateScan
since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0) since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0)
sql := GroupDataBy("hits", s.Id, since, "minute") sql := GroupDataBy("hits", s.Id, since, "minute")
rows, err := DbSession.Raw(sql).Rows() rows, err := DbSession.Raw(sql).Rows()
defer rows.Close()
if err != nil { if err != nil {
utils.Log(2, err) utils.Log(2, err)
return "" return ""
@ -175,11 +193,13 @@ func (s *Service) GraphData() string {
return string(data) return string(data)
} }
// AvgUptime24 returns a service's average online status for last 24 hours
func (s *Service) AvgUptime24() string { func (s *Service) AvgUptime24() string {
ago := time.Now().Add(-24 * time.Hour) ago := time.Now().Add(-24 * time.Hour)
return s.AvgUptime(ago) return s.AvgUptime(ago)
} }
// AvgUptime returns average online status for last 24 hours
func (s *Service) AvgUptime(ago time.Time) string { func (s *Service) AvgUptime(ago time.Time) string {
failed, _ := s.TotalFailuresSince(ago) failed, _ := s.TotalFailuresSince(ago)
if failed == 0 { if failed == 0 {
@ -201,6 +221,7 @@ func (s *Service) AvgUptime(ago time.Time) string {
return amount return amount
} }
// TotalUptime returns the total uptime percent of a service
func (s *Service) TotalUptime() string { func (s *Service) TotalUptime() string {
hits, _ := s.TotalHits() hits, _ := s.TotalHits()
failures, _ := s.TotalFailures() failures, _ := s.TotalFailures()
@ -216,6 +237,7 @@ func (s *Service) TotalUptime() string {
return amount return amount
} }
// index returns a services index int for updating the []*core.Services slice
func (s *Service) index() int { func (s *Service) index() int {
for k, service := range CoreApp.Services { for k, service := range CoreApp.Services {
if s.Id == service.(*Service).Id { if s.Id == service.(*Service).Id {
@ -225,11 +247,13 @@ func (s *Service) index() int {
return 0 return 0
} }
// updateService will update a service in the []*core.Services slice
func updateService(service *Service) { func updateService(service *Service) {
index := service.index() index := service.index()
CoreApp.Services[index] = service CoreApp.Services[index] = service
} }
// Delete will remove a service from the database, it will also end the service checking go routine
func (u *Service) Delete() error { func (u *Service) Delete() error {
i := u.index() i := u.index()
err := servicesDB().Delete(u) err := servicesDB().Delete(u)
@ -240,14 +264,17 @@ func (u *Service) Delete() error {
u.Close() u.Close()
slice := CoreApp.Services slice := CoreApp.Services
CoreApp.Services = append(slice[:i], slice[i+1:]...) CoreApp.Services = append(slice[:i], slice[i+1:]...)
//OnDeletedService(u) reorderServices()
notifiers.OnDeletedService(u.Service)
return err.Error return err.Error
} }
// UpdateSingle will update a single column for a service
func (u *Service) UpdateSingle(attr ...interface{}) error { func (u *Service) UpdateSingle(attr ...interface{}) error {
return servicesDB().Model(u).Update(attr).Error return servicesDB().Model(u).Update(attr).Error
} }
// Update will update a service in the database, the service's checking routine can be restarted by passing true
func (u *Service) Update(restart bool) error { func (u *Service) Update(restart bool) error {
err := servicesDB().Update(u) err := servicesDB().Update(u)
if err.Error != nil { if err.Error != nil {
@ -260,11 +287,13 @@ func (u *Service) Update(restart bool) error {
u.SleepDuration = time.Duration(u.Interval) * time.Second u.SleepDuration = time.Duration(u.Interval) * time.Second
go u.CheckQueue(true) go u.CheckQueue(true)
} }
reorderServices()
updateService(u) updateService(u)
//OnUpdateService(u) notifiers.OnUpdatedService(u.Service)
return err.Error return err.Error
} }
// Create will create a service and insert it into the database
func (u *Service) Create() (int64, error) { func (u *Service) Create() (int64, error) {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
db := servicesDB().Create(u) db := servicesDB().Create(u)
@ -275,13 +304,17 @@ func (u *Service) Create() (int64, error) {
u.Start() u.Start()
go u.CheckQueue(true) go u.CheckQueue(true)
CoreApp.Services = append(CoreApp.Services, u) CoreApp.Services = append(CoreApp.Services, u)
reorderServices()
notifiers.OnNewService(u.Service)
return u.Id, nil return u.Id, nil
} }
// ServicesCount returns the amount of services inside the []*core.Services slice
func (c *Core) ServicesCount() int { func (c *Core) ServicesCount() int {
return len(c.Services) return len(c.Services)
} }
// CountOnline
func (c *Core) CountOnline() int { func (c *Core) CountOnline() int {
amount := 0 amount := 0
for _, s := range CoreApp.Services { for _, s := range CoreApp.Services {

View File

@ -22,6 +22,7 @@ import (
"os" "os"
) )
// DeleteConfig will delete the 'config.yml' file
func DeleteConfig() { func DeleteConfig() {
err := os.Remove(utils.Directory + "/config.yml") err := os.Remove(utils.Directory + "/config.yml")
if err != nil { if err != nil {
@ -33,6 +34,7 @@ type ErrorResponse struct {
Error string Error string
} }
// LoadSampleData will create the example/dummy services for a brand new Statup installation
func LoadSampleData() error { func LoadSampleData() error {
utils.Log(1, "Inserting Sample Data...") utils.Log(1, "Inserting Sample Data...")
s1 := ReturnService(&types.Service{ s1 := ReturnService(&types.Service{

View File

@ -27,16 +27,19 @@ type User struct {
*types.User *types.User
} }
// ReturnUser returns *core.User based off a *types.User
func ReturnUser(u *types.User) *User { func ReturnUser(u *types.User) *User {
return &User{u} return &User{u}
} }
// SelectUser returns the User based on the user's ID.
func SelectUser(id int64) (*User, error) { func SelectUser(id int64) (*User, error) {
var user User var user User
err := usersDB().First(&user, id) err := usersDB().First(&user, id)
return &user, err.Error return &user, err.Error
} }
// SelectUser returns the User based on the user's username
func SelectUsername(username string) (*User, error) { func SelectUsername(username string) (*User, error) {
var user User var user User
res := usersDB().Where("username = ?", username) res := usersDB().Where("username = ?", username)
@ -44,15 +47,18 @@ func SelectUsername(username string) (*User, error) {
return &user, err.Error return &user, err.Error
} }
// Delete will remove the user record from the database
func (u *User) Delete() error { func (u *User) Delete() error {
return usersDB().Delete(u).Error return usersDB().Delete(u).Error
} }
// Update will update the user's record in database
func (u *User) Update() error { func (u *User) Update() error {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
return usersDB().Update(u).Error return usersDB().Update(u).Error
} }
// Create will insert a new user into the database
func (u *User) Create() (int64, error) { func (u *User) Create() (int64, error) {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
u.Password = utils.HashPassword(u.Password) u.Password = utils.HashPassword(u.Password)
@ -69,6 +75,7 @@ func (u *User) Create() (int64, error) {
return u.Id, db.Error return u.Id, db.Error
} }
// SelectAllUsers returns all users
func SelectAllUsers() ([]*User, error) { func SelectAllUsers() ([]*User, error) {
var users []*User var users []*User
db := usersDB().Find(&users) db := usersDB().Find(&users)
@ -78,6 +85,8 @@ func SelectAllUsers() ([]*User, error) {
return users, db.Error return users, db.Error
} }
// AuthUser will return the User and a boolean if authentication was correct.
// AuthUser accepts username, and password as a string
func AuthUser(username, password string) (*User, bool) { func AuthUser(username, password string) (*User, bool) {
user, err := SelectUsername(username) user, err := SelectUsername(username)
if err != nil { if err != nil {
@ -90,6 +99,7 @@ func AuthUser(username, password string) (*User, bool) {
return nil, false return nil, false
} }
// CheckHash returns true if the password matches with a hashed bcrypt password
func CheckHash(password, hash string) bool { func CheckHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil return err == nil

View File

@ -17,7 +17,7 @@ package notifiers
import "github.com/hunterlong/statup/types" import "github.com/hunterlong/statup/types"
// Notifier interface // OnSave will trigger a notifier when it has been saved - Notifier interface
func OnSave(method string) { func OnSave(method string) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "Notifier") { if IsType(comm, "Notifier") {
@ -29,7 +29,7 @@ func OnSave(method string) {
} }
} }
// BasicEvents interface // OnFailure will be triggered when a service is failing - BasicEvents interface
func OnFailure(s *types.Service, f *types.Failure) { func OnFailure(s *types.Service, f *types.Failure) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "BasicEvents") { if IsType(comm, "BasicEvents") {
@ -38,7 +38,7 @@ func OnFailure(s *types.Service, f *types.Failure) {
} }
} }
// BasicEvents interface // OnSuccess will be triggered when a service is successful - BasicEvents interface
func OnSuccess(s *types.Service) { func OnSuccess(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "BasicEvents") { if IsType(comm, "BasicEvents") {
@ -47,7 +47,7 @@ func OnSuccess(s *types.Service) {
} }
} }
// ServiceEvents interface // OnNewService is triggered when a new service is created - ServiceEvents interface
func OnNewService(s *types.Service) { func OnNewService(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "ServiceEvents") { if IsType(comm, "ServiceEvents") {
@ -56,7 +56,7 @@ func OnNewService(s *types.Service) {
} }
} }
// ServiceEvents interface // OnUpdatedService is triggered when a service is updated - ServiceEvents interface
func OnUpdatedService(s *types.Service) { func OnUpdatedService(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "ServiceEvents") { if IsType(comm, "ServiceEvents") {
@ -65,7 +65,7 @@ func OnUpdatedService(s *types.Service) {
} }
} }
// ServiceEvents interface // OnDeletedService is triggered when a service is deleted - ServiceEvents interface
func OnDeletedService(s *types.Service) { func OnDeletedService(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "ServiceEvents") { if IsType(comm, "ServiceEvents") {
@ -74,7 +74,7 @@ func OnDeletedService(s *types.Service) {
} }
} }
// UserEvents interface // OnNewUser is triggered when a new user is created - UserEvents interface
func OnNewUser(u *types.User) { func OnNewUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "UserEvents") { if IsType(comm, "UserEvents") {
@ -83,7 +83,7 @@ func OnNewUser(u *types.User) {
} }
} }
// UserEvents interface // OnUpdatedUser is triggered when a new user is updated - UserEvents interface
func OnUpdatedUser(u *types.User) { func OnUpdatedUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "UserEvents") { if IsType(comm, "UserEvents") {
@ -92,7 +92,7 @@ func OnUpdatedUser(u *types.User) {
} }
} }
// UserEvents interface // OnDeletedUser is triggered when a new user is deleted - UserEvents interface
func OnDeletedUser(u *types.User) { func OnDeletedUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "UserEvents") { if IsType(comm, "UserEvents") {
@ -101,7 +101,7 @@ func OnDeletedUser(u *types.User) {
} }
} }
// CoreEvents interface // OnUpdatedCore is triggered when the CoreApp settings are saved - CoreEvents interface
func OnUpdatedCore(c *types.Core) { func OnUpdatedCore(c *types.Core) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if IsType(comm, "CoreEvents") { if IsType(comm, "CoreEvents") {

View File

@ -19,17 +19,25 @@ import "github.com/hunterlong/statup/types"
// Notifier interface is required to create a new Notifier // Notifier interface is required to create a new Notifier
type Notifier interface { type Notifier interface {
// Init will load and install the notifier if needed
Init() error Init() error
// Install will install the notifier into the database
Install() error Install() error
// Run will trigger inside of the notifier when enabled
Run() error Run() error
// OnSave is triggered when the notifier is saved
OnSave() error OnSave() error
// Test will run a function inside the notifier to Test if it works
Test() error Test() error
// Select returns the *Notification for a notifier
Select() *Notification Select() *Notification
} }
// BasicEvents includes the basic events, failing and successful service triggers // BasicEvents includes the most minimal events, failing and successful service triggers
type BasicEvents interface { type BasicEvents interface {
// OnSuccess is triggered when a service is successful
OnSuccess(*types.Service) OnSuccess(*types.Service)
// OnFailure is triggered when a service is failing
OnFailure(*types.Service, *types.Failure) OnFailure(*types.Service, *types.Failure)
} }

View File

@ -27,12 +27,13 @@ import (
) )
var ( var (
CssBox *rice.Box CssBox *rice.Box // CSS files from the 'source/css' directory, this will be loaded into '/assets/css'
ScssBox *rice.Box ScssBox *rice.Box // SCSS files from the 'source/scss' directory, this will be loaded into '/assets/scss'
JsBox *rice.Box JsBox *rice.Box // JS files from the 'source/js' directory, this will be loaded into '/assets/js'
TmplBox *rice.Box TmplBox *rice.Box // HTML and other small files from the 'source/tmpl' directory, this will be loaded into '/assets'
) )
// Assets will load the Rice boxes containing the CSS, SCSS, JS, and HTML files.
func Assets() { func Assets() {
CssBox = rice.MustFindBox("css") CssBox = rice.MustFindBox("css")
ScssBox = rice.MustFindBox("scss") ScssBox = rice.MustFindBox("scss")
@ -40,6 +41,7 @@ func Assets() {
TmplBox = rice.MustFindBox("tmpl") TmplBox = rice.MustFindBox("tmpl")
} }
// CompileSASS will attempt to compile the SASS files into CSS
func CompileSASS(folder string) error { func CompileSASS(folder string) error {
sassBin := os.Getenv("SASS") sassBin := os.Getenv("SASS")
if sassBin == "" { if sassBin == "" {
@ -93,6 +95,7 @@ func CompileSASS(folder string) error {
return err return err
} }
// UsingAssets returns true if the '/assets' folder is found in the directory
func UsingAssets(folder string) bool { func UsingAssets(folder string) bool {
if _, err := os.Stat(folder + "/assets"); err == nil { if _, err := os.Stat(folder + "/assets"); err == nil {
return true return true
@ -112,6 +115,7 @@ func UsingAssets(folder string) bool {
return false return false
} }
// SaveAsset will save an asset to the '/assets/' folder.
func SaveAsset(data []byte, folder, file string) error { func SaveAsset(data []byte, folder, file string) error {
utils.Log(1, fmt.Sprintf("Saving %v/%v into assets folder", folder, file)) utils.Log(1, fmt.Sprintf("Saving %v/%v into assets folder", folder, file))
err := ioutil.WriteFile(folder+"/assets/"+file, data, 0744) err := ioutil.WriteFile(folder+"/assets/"+file, data, 0744)
@ -122,6 +126,7 @@ func SaveAsset(data []byte, folder, file string) error {
return nil return nil
} }
// OpenAsset returns a file's contents as a string
func OpenAsset(folder, file string) string { func OpenAsset(folder, file string) string {
dat, err := ioutil.ReadFile(folder + "/assets/" + file) dat, err := ioutil.ReadFile(folder + "/assets/" + file)
if err != nil { if err != nil {
@ -131,6 +136,7 @@ func OpenAsset(folder, file string) string {
return string(dat) return string(dat)
} }
// CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory
func CreateAllAssets(folder string) error { func CreateAllAssets(folder string) error {
utils.Log(1, fmt.Sprintf("Dump Statup assets into %v/assets", folder)) utils.Log(1, fmt.Sprintf("Dump Statup assets into %v/assets", folder))
MakePublicFolder(folder + "/assets") MakePublicFolder(folder + "/assets")
@ -156,6 +162,7 @@ func CreateAllAssets(folder string) error {
return err return err
} }
// DeleteAllAssets will delete the '/assets' folder
func DeleteAllAssets(folder string) error { func DeleteAllAssets(folder string) error {
err := os.RemoveAll(folder + "/assets") err := os.RemoveAll(folder + "/assets")
if err != nil { if err != nil {
@ -166,6 +173,7 @@ func DeleteAllAssets(folder string) error {
return err return err
} }
// CopyToPublic will create a file from a rice Box to the '/assets' directory
func CopyToPublic(box *rice.Box, folder, file string) error { func CopyToPublic(box *rice.Box, folder, file string) error {
assetFolder := fmt.Sprintf("%v/%v", folder, file) assetFolder := fmt.Sprintf("%v/%v", folder, file)
utils.Log(1, fmt.Sprintf("Copying %v to %v", file, assetFolder)) utils.Log(1, fmt.Sprintf("Copying %v to %v", file, assetFolder))
@ -182,6 +190,7 @@ func CopyToPublic(box *rice.Box, folder, file string) error {
return nil return nil
} }
// MakePublicFolder will create a new folder
func MakePublicFolder(folder string) error { func MakePublicFolder(folder string) error {
utils.Log(1, fmt.Sprintf("Creating folder '%v'", folder)) utils.Log(1, fmt.Sprintf("Creating folder '%v'", folder))
if _, err := os.Stat(folder); os.IsNotExist(err) { if _, err := os.Stat(folder); os.IsNotExist(err) {
@ -194,6 +203,7 @@ func MakePublicFolder(folder string) error {
return nil return nil
} }
// copyAndCapture captures the response from a terminal command
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) { func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
var out []byte var out []byte
buf := make([]byte, 1024, 1024) buf := make([]byte, 1024, 1024)

View File

@ -31,12 +31,6 @@ type Core struct {
CoreInterface `gorm:"-" json:"-"` CoreInterface `gorm:"-" json:"-"`
} }
type ServiceOrder []*Service
func (c ServiceOrder) Len() int { return len(c) }
func (c ServiceOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ServiceOrder) Less(i, j int) bool { return c[i].Order < c[j].Order }
type CoreInterface interface { type CoreInterface interface {
SelectAllServices() ([]*Service, error) SelectAllServices() ([]*Service, error)
Count24HFailures() uint64 Count24HFailures() uint64

View File

@ -14,6 +14,6 @@ type Failure struct {
} }
type FailureInterface interface { type FailureInterface interface {
Ago() string Ago() string // Ago returns a human readble timestamp
ParseError() string ParseError() string // ParseError returns a human readable error for a service failure
} }

View File

@ -89,16 +89,19 @@ type ServiceInterface interface {
AllCheckins() []*Checkin AllCheckins() []*Checkin
} }
// Start will create a channel for the service checking go routine
func (s *Service) Start() { func (s *Service) Start() {
s.Running = make(chan bool) s.Running = make(chan bool)
} }
// Close will stop the go routine that is checking if service is online or not
func (s *Service) Close() { func (s *Service) Close() {
if s.IsRunning() { if s.IsRunning() {
close(s.Running) close(s.Running)
} }
} }
// IsRunning returns true if the service go routine is running
func (s *Service) IsRunning() bool { func (s *Service) IsRunning() bool {
if s.Running == nil { if s.Running == nil {
return false return false

View File

@ -23,11 +23,13 @@ import (
"math/rand" "math/rand"
) )
// HashPassword returns the bcrypt hash of a password string
func HashPassword(password string) string { func HashPassword(password string) string {
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), 14) bytes, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes) return string(bytes)
} }
// NewSHA1Hash returns a random SHA1 hash based on a specific length
func NewSHA1Hash(n ...int) string { func NewSHA1Hash(n ...int) string {
noRandomCharacters := 32 noRandomCharacters := 32
if len(n) > 0 { if len(n) > 0 {
@ -51,6 +53,7 @@ func RandomString(n int) string {
return string(b) return string(b)
} }
// Sha256 returns a SHA256 hash as string from []byte
func Sha256(data []byte) string { func Sha256(data []byte) string {
h := sha1.New() h := sha1.New()
h.Write(data) h.Write(data)

View File

@ -38,6 +38,7 @@ func Logger() *lumberjack.Logger {
return ljLogger return ljLogger
} }
// createLog will create the '/logs' directory based on a directory
func createLog(dir string) error { func createLog(dir string) error {
var err error var err error
_, err = os.Stat(dir + "/logs") _, err = os.Stat(dir + "/logs")
@ -56,6 +57,7 @@ func createLog(dir string) error {
return err return err
} }
// InitLogs will create the '/logs' directory and creates a file '/logs/statup.log' for application logging
func InitLogs() error { func InitLogs() error {
err := createLog(Directory) err := createLog(Directory)
if err != nil { if err != nil {
@ -90,6 +92,7 @@ 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 { func Log(level int, err interface{}) error {
pushLastLine(err) pushLastLine(err)
var outErr error var outErr error
@ -122,6 +125,7 @@ func Log(level int, err interface{}) error {
return outErr return outErr
} }
// Http returns a log for a HTTP request
func Http(r *http.Request) string { func Http(r *http.Request) string {
msg := fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host) msg := fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host)
fmtLogs.Printf("WEB: %v\n", msg) fmtLogs.Printf("WEB: %v\n", msg)
@ -140,6 +144,7 @@ func pushLastLine(line interface{}) {
} }
} }
// GetLastLine returns 1 line for a recent log entry
func GetLastLine() *LogRow { func GetLastLine() *LogRow {
LockLines.Lock() LockLines.Lock()
defer LockLines.Unlock() defer LockLines.Unlock()

View File

@ -31,6 +31,7 @@ var (
Directory string Directory string
) )
// init will set the utils.Directory to the current running directory, or STATUP_DIR if it is set
func init() { func init() {
if os.Getenv("STATUP_DIR") != "" { if os.Getenv("STATUP_DIR") != "" {
Directory = os.Getenv("STATUP_DIR") Directory = os.Getenv("STATUP_DIR")
@ -39,15 +40,18 @@ func init() {
} }
} }
// StringInt converts a string to an int64
func StringInt(s string) int64 { func StringInt(s string) int64 {
num, _ := strconv.Atoi(s) num, _ := strconv.Atoi(s)
return int64(num) return int64(num)
} }
// IntString converts a int to a string
func IntString(s int) string { func IntString(s int) string {
return strconv.Itoa(s) return strconv.Itoa(s)
} }
// dir returns the current working directory
func dir() string { func dir() string {
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { if err != nil {
@ -57,16 +61,17 @@ func dir() string {
} }
type Timestamp time.Time type Timestamp time.Time
type Timestamper interface { type Timestamper interface {
Ago() string Ago() string
} }
// Ago returns a human readable timestamp based on the Timestamp (time.Time) interface
func (t Timestamp) Ago() string { func (t Timestamp) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), time.Time(t)) got, _ := timeago.TimeAgoWithTime(time.Now(), time.Time(t))
return got return got
} }
// UnderScoreString will return a string that replaces spaces and other characters to underscores
func UnderScoreString(str string) string { func UnderScoreString(str string) string {
// convert every letter to lower case // convert every letter to lower case
@ -90,6 +95,7 @@ func UnderScoreString(str string) string {
return newStr return newStr
} }
// FileExists returns true if a file exists
func FileExists(name string) bool { func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil { if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -99,6 +105,7 @@ func FileExists(name string) bool {
return true return true
} }
// DeleteFile will attempt to delete a file
func DeleteFile(file string) error { func DeleteFile(file string) error {
Log(1, "deleting file: "+file) Log(1, "deleting file: "+file)
err := os.Remove(file) err := os.Remove(file)
@ -108,10 +115,12 @@ func DeleteFile(file string) error {
return nil return nil
} }
// DeleteDirectory will attempt to delete a directory and all contents inside
func DeleteDirectory(directory string) error { func DeleteDirectory(directory string) error {
return os.RemoveAll(directory) return os.RemoveAll(directory)
} }
// Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings
func Command(cmd string) (string, string, error) { func Command(cmd string) (string, string, error) {
Log(1, "running command: "+cmd) Log(1, "running command: "+cmd)
testCmd := exec.Command("sh", "-c", cmd) testCmd := exec.Command("sh", "-c", cmd)
@ -142,6 +151,7 @@ func Command(cmd string) (string, string, error) {
return outStr, errStr, err return outStr, errStr, err
} }
// copyAndCapture will read a terminal command into bytes
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) { func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
var out []byte var out []byte
buf := make([]byte, 1024, 1024) buf := make([]byte, 1024, 1024)