From 0e364b0aac592c4733cffe85e0b9ec75836121da Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Mon, 10 Sep 2018 15:16:23 -0700 Subject: [PATCH] comments - core updates - db connection close fix --- Dockerfile | 2 +- Gopkg.lock | 4 +- Makefile | 157 ++++++++++++++++++++++++++++------------ README.md | 2 + cmd/cli.go | 3 + cmd/main.go | 6 ++ cmd/main_test.go | 8 +- core/checker.go | 27 ++++--- core/configs.go | 2 + core/core.go | 17 +---- core/database.go | 16 ++-- core/failures.go | 13 +++- core/hits.go | 8 ++ core/services.go | 39 +++++++++- core/setup.go | 2 + core/users.go | 10 +++ notifiers/events.go | 20 ++--- notifiers/interfaces.go | 10 ++- source/source.go | 18 ++++- types/core.go | 6 -- types/failure.go | 4 +- types/service.go | 3 + utils/encryption.go | 3 + utils/log.go | 5 ++ utils/utils.go | 12 ++- 25 files changed, 287 insertions(+), 110 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1d7c6f9d..d29cffed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hunterlong/statup:base-v0.56 +FROM hunterlong/statup:base-v0.57 MAINTAINER "Hunter Long (https://github.com/hunterlong)" # Locked version of Statup for 'latest' Docker tag diff --git a/Gopkg.lock b/Gopkg.lock index 7b6fc3f8..3df9541a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -284,14 +284,14 @@ [[projects]] branch = "master" - digest = "1:7f4a61b989d94774dc61016b660cf8347f59eb0bed91a10b2f23fc72a38d45d4" + digest = "1:374fc90fcb026e9a367e3fad29e988e5dd944b68ca3f24a184d77abc5307dda4" name = "golang.org/x/sys" packages = [ "unix", "windows", ] pruneopts = "UT" - revision = "ebe1bf3edb3325c393447059974de898d5133eb8" + revision = "d0be0721c37eeb5299f245a996a483160fc36940" [[projects]] digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" diff --git a/Makefile b/Makefile index 64f7e96f..c01bd07c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=0.56 +VERSION=0.57 BINARY_NAME=statup GOPATH:=$(GOPATH) 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" ] } } }' 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 -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: - rm -f statup.db - cat dev/seed.sql | sqlite3 statup.db +# build all docker tags +docker-build-all: docker-build-base docker-build-dev docker-build-latest +# 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 $(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd +# build Statup plugins build-plugin: $(GOBUILD) $(BUILDVERSION) -buildmode=plugin -o $(BINARY_NAME) -v ./dev/plugin +# build Statup debug app build-debug: compile $(GOBUILD) $(BUILDVERSION) -tags debug -o $(BINARY_NAME) -v ./cmd +# install Statup for local arch and move binary to gopath/src/bin/statup install: build mv $(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME) $(GOPATH)/bin/$(BINARY_NAME) version +# run Statup from local arch run: build ./$(BINARY_NAME) --ip 0.0.0.0 --port 8080 +# compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go compile: cd source && $(GOPATH)/bin/rice embed-go sass source/scss/base.scss source/css/base.css rm -rf .sass-cache +# benchmark testing benchmark: cd handlers && go test -v -run=^$ -bench=. -benchtime=5s -memprofile=prof.mem -cpuprofile=prof.cpu +# view benchmark testing using pprof benchmark-view: go tool pprof handlers/handlers.test handlers/prof.cpu > top20 +# test Statup golang tetsing files test: clean compile install STATUP_DIR=$(TEST_DIR) go test -v -p=1 $(BUILDVERSION) -coverprofile=coverage.out ./... gocov convert coverage.out > coverage.json +# report coverage to Coveralls coverage: $(GOPATH)/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $(COVERALLS) +# generate documentation for Statup functions docs: godoc2md github.com/hunterlong/statup > servers/docs/README.md gocov-html coverage.json > servers/docs/COVERAGE.html 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 $(XGO) $(BUILDVERSION) --targets=darwin/amd64 ./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/386 ./cmd - $(XGO) $(BUILDVERSION) --targets=windows-6.0/amd64 ./cmd $(XGO) $(BUILDVERSION) --targets=linux/arm-7 ./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 $(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-push-dev: - docker push hunterlong/statup:dev - -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: +# build :base and base-v{VERSION} docker tag +docker-build-base: clean 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 docker build -t hunterlong/statup:base --no-cache -f dev/Dockerfile-base . docker tag hunterlong/statup:base hunterlong/statup:base-v$(VERSION) -docker-build-latest: - docker build -t hunterlong/statup:latest --no-cache -f Dockerfile . +# build Cypress UI testing :cypress docker tag +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: 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 sleep 30 + +# +# Download and Install dependencies +# + +# run dep to install all required golang dependecies dep: dep ensure -vendor-only +# install all required golang dependecies dev-deps: dep $(GOGET) -u github.com/jinzhu/gorm/... $(GOGET) github.com/stretchr/testify/assert @@ -153,6 +206,7 @@ dev-deps: dep $(GOCMD) install gopkg.in/matm/v1/gocov-html $(GOCMD) get github.com/mgechev/revive +# remove files for a clean compile/build 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 cmd/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log} @@ -169,9 +223,11 @@ clean: find . -name "*.mem" -type f -delete find . -name "*.test" -type f -delete +# tag version using git tag: git tag "v$(VERSION)" --force +# compress built binaries into tar.gz and zip formats compress: cd build && mv alpine-linux-amd64 $(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 && tar -czvf $(BINARY_NAME)-linux-arm64.tar.gz $(BINARY_NAME) && rm -f $(BINARY_NAME) +# push the :dev docker tag using curl publish-dev: curl -H "Content-Type: application/json" --data '{"docker_tag": "dev"}' -X POST $(DOCKER) +# push the :latest docker tag using curl publish-latest: curl -H "Content-Type: application/json" --data '{"docker_tag": "latest"}' -X POST $(DOCKER) +# update the homebrew application to latest for mac publish-homebrew: curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $(TRAVIS_API)" -d $(PUBLISH_BODY) https://api.travis-ci.com/repo/hunterlong%2Fhomebrew-statup/requests +# install NPM reuqirements for cypress testing cypress-install: cd dev/test && npm install +# run Cypress UI testing cypress-test: clean cypress-install cd dev/test && npm test +# build Statup using a travis ci trigger 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 +# install xgo and pull the xgo docker image xgo-install: clean go get github.com/karalabe/xgo docker pull karalabe/xgo-latest -.PHONY: build build-all build-alpine test-all test +.PHONY: all build build-all build-alpine test-all test diff --git a/README.md b/README.md index 83b45ca0..d3e8a9bc 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ # 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. +[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/statup-app/general) + ## 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. diff --git a/cmd/cli.go b/cmd/cli.go index 8c59e1ea..3acf5bf2 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -35,6 +35,7 @@ const ( POINT = " " ) +// CatchCLI will run functions based on the commands sent to Statup func CatchCLI(args []string) error { dir := utils.Directory utils.InitLogs() @@ -129,6 +130,7 @@ func CatchCLI(args []string) error { return errors.New("end") } +// RunOnce will initialize the Statup application and check each service 1 time, will not run HTTP server func RunOnce() { var err error core.Configs, err = core.LoadConfig(utils.Directory) @@ -154,6 +156,7 @@ func RunOnce() { } } +// HelpEcho prints out available commands and flags for Statup func HelpEcho() { fmt.Printf("Statup v%v - Statup.io\n", VERSION) fmt.Printf("A simple Application Status Monitor that is opensource and lightweight.\n") diff --git a/cmd/main.go b/cmd/main.go index 37cf8274..8fe2ebb9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -39,6 +39,9 @@ func init() { 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() { 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") @@ -47,6 +50,7 @@ func parseFlags() { port = *p } +// main will run the Statup application func main() { var err error parseFlags() @@ -76,6 +80,7 @@ func main() { mainProcess() } +// LoadDotEnvs attempts to load database configs from a '.env' file in root directory func LoadDotEnvs() error { err := godotenv.Load() if err == nil { @@ -85,6 +90,7 @@ func LoadDotEnvs() error { return err } +// mainProcess will initialize the Statup application and run the HTTP server func mainProcess() { dir := utils.Directory var err error diff --git a/cmd/main_test.go b/cmd/main_test.go index 4d07574e..f9bb5064 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -418,10 +418,10 @@ func RunService_Online24(t *testing.T) { online = service.OnlineSince(SERVICE_SINCE) assert.Equal(t, float32(0), online) - service = core.SelectService(18) - assert.NotNil(t, service) - online = service.OnlineSince(SERVICE_SINCE) - assert.Equal(t, float32(0), online) + //service = core.SelectService(18) + //assert.NotNil(t, service) + //online = service.OnlineSince(SERVICE_SINCE) + //assert.Equal(t, float32(0), online) } func RunService_GraphData(t *testing.T) { diff --git a/core/checker.go b/core/checker.go index 728b5bb6..3c47d17a 100644 --- a/core/checker.go +++ b/core/checker.go @@ -30,6 +30,7 @@ import ( "time" ) +// CheckServices will start the checking go routine for each service func CheckServices() { utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services))) 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) { s.Checkpoint = time.Now() 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 { var amount time.Duration if s.Interval >= 10000 { @@ -71,6 +74,7 @@ func (s *Service) duration() time.Duration { 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) { t1 := time.Now() domain := s.Domain @@ -92,6 +96,7 @@ func (s *Service) dnsCheck() (float64, error) { return subTime, err } +// checkTcp will check a TCP service func (s *Service) checkTcp(record bool) *Service { t1 := time.Now() domain := fmt.Sprintf("%v", s.Domain) @@ -120,15 +125,7 @@ func (s *Service) checkTcp(record bool) *Service { return s } -func (s *Service) Check(record bool) { - switch s.Type { - case "http": - s.checkHttp(record) - case "tcp": - s.checkTcp(record) - } -} - +// checkHttp will check a HTTP service func (s *Service) checkHttp(record bool) *Service { dnsLookup, err := s.dnsCheck() if err != nil { @@ -202,10 +199,21 @@ func (s *Service) checkHttp(record bool) *Service { 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 { Latency float64 } +// RecordSuccess will create a new 'hit' record in the database for a successful/online service func RecordSuccess(s *Service) { s.Online = true s.LastOnline = time.Now() @@ -219,6 +227,7 @@ func RecordSuccess(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) { s.Online = false fail := &types.Failure{ diff --git a/core/configs.go b/core/configs.go index 23068cb5..f0953baa 100644 --- a/core/configs.go +++ b/core/configs.go @@ -25,6 +25,7 @@ import ( "os" ) +// LoadConfig will attempt to load the 'config.yml' file in a specific directory func LoadConfig(directory string) (*DbConfig, error) { var configs *types.DbConfig if os.Getenv("DB_CONN") != "" { @@ -43,6 +44,7 @@ func LoadConfig(directory string) (*DbConfig, error) { 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) { Configs = new(DbConfig) if os.Getenv("DB_CONN") == "" { diff --git a/core/core.go b/core/core.go index e9bd14a1..68455581 100644 --- a/core/core.go +++ b/core/core.go @@ -54,6 +54,7 @@ func (c *Core) ToCore() *types.Core { return c.Core } +// InitApp will initialize Statup func InitApp() { SelectCore() InsertNotifierDB() @@ -151,20 +152,8 @@ func SelectCore() (*Core, error) { } // 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) 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 } - -// 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 -//} +func (c ServiceOrder) Less(i, j int) bool { return c[i].(*Service).Order < c[j].(*Service).Order } diff --git a/core/database.go b/core/database.go index cda1d0a5..5779e635 100644 --- a/core/database.go +++ b/core/database.go @@ -34,35 +34,42 @@ var ( DbSession *gorm.DB ) -func failuresDB() *gorm.DB { - return DbSession.Model(&types.Failure{}) -} - func (s *Service) allHits() *gorm.DB { var hits []*Hit 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 { return DbSession.Model(&types.Hit{}) } +// servicesDB returns the 'services' database column func servicesDB() *gorm.DB { return DbSession.Model(&types.Service{}) } +// coreDB returns the single column 'core' func coreDB() *gorm.DB { return DbSession.Table("core").Model(&CoreApp) } +// usersDB returns the 'users' database column func usersDB() *gorm.DB { return DbSession.Model(&types.User{}) } +// commDB returns the 'communications' database column func commDB() *gorm.DB { return DbSession.Table("communication").Model(¬ifiers.Notification{}) } +// hitsDB returns the 'hits' database column func checkinDB() *gorm.DB { return DbSession.Model(&types.Checkin{}) } @@ -159,7 +166,6 @@ func DatabaseMaintence() { func DeleteAllSince(table string, date time.Time) { sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02")) db := DbSession.Raw(sql) - defer db.Close() if db.Error != nil { utils.Log(2, db.Error) } diff --git a/core/failures.go b/core/failures.go index 948acba3..669c598e 100644 --- a/core/failures.go +++ b/core/failures.go @@ -28,8 +28,8 @@ type Failure struct { *types.Failure } +// CreateFailure will create a new failure record for a service func (s *Service) CreateFailure(f *types.Failure) (int64, error) { - f.CreatedAt = time.Now() f.Service = s.Id s.Failures = append(s.Failures, f) row := failuresDB().Create(f) @@ -40,6 +40,7 @@ func (s *Service) CreateFailure(f *types.Failure) (int64, error) { return f.Id, row.Error } +// AllFailures will return all failures attached to a service func (s *Service) AllFailures() []*Failure { var fails []*Failure col := failuresDB().Where("service = ?", s.Id).Order("id desc") @@ -54,6 +55,7 @@ func (s *Service) AllFailures() []*Failure { return fails } +// DeleteFailures will delete all failures for a service func (u *Service) DeleteFailures() { err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, u.Id) if err.Error != nil { @@ -62,6 +64,7 @@ func (u *Service) DeleteFailures() { u.Failures = nil } +// LimitedFailures will return the last 10 failures from a service func (s *Service) LimitedFailures() []*Failure { var failArr []*Failure col := failuresDB().Where("service = ?", s.Id).Order("id desc").Limit(10) @@ -69,16 +72,19 @@ func (s *Service) LimitedFailures() []*Failure { return failArr } +// Ago returns a human readable timestamp for a failure func (f *Failure) Ago() string { got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt) return got } +// Delete will remove a failure record from the database func (f *Failure) Delete() error { db := failuresDB().Delete(f) return db.Error } +// Count24HFailures returns the amount of failures for a service within the last 24 hours func (c *Core) Count24HFailures() uint64 { var count uint64 for _, s := range CoreApp.Services { @@ -89,6 +95,7 @@ func (c *Core) Count24HFailures() uint64 { return count } +// CountFailures returns the total count of failures for all services func CountFailures() uint64 { var count uint64 err := failuresDB().Count(&count) @@ -99,11 +106,13 @@ func CountFailures() uint64 { return count } +// TotalFailures24 returns the amount of failures for a service within the last 24 hours func (s *Service) TotalFailures24() (uint64, error) { ago := time.Now().Add(-24 * time.Hour) return s.TotalFailuresSince(ago) } +// TotalFailures returns the total amount of failures for a service func (s *Service) TotalFailures() (uint64, error) { var count uint64 rows := failuresDB().Where("service = ?", s.Id) @@ -111,6 +120,7 @@ func (s *Service) TotalFailures() (uint64, 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) { var count uint64 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 } +// ParseError returns a human readable error for a failure func (f *Failure) ParseError() string { err := strings.Contains(f.Issue, "connection reset by peer") if err { diff --git a/core/hits.go b/core/hits.go index 73c7f2ab..409cf8a5 100644 --- a/core/hits.go +++ b/core/hits.go @@ -25,6 +25,7 @@ type Hit struct { *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) { db := hitsDB().Create(h) if db.Error != nil { @@ -34,6 +35,7 @@ func (s *Service) CreateHit(h *types.Hit) (int64, error) { return h.Id, db.Error } +// Hits returns all successful hits for a service func (s *Service) Hits() ([]*types.Hit, error) { var hits []*types.Hit col := hitsDB().Where("service = ?", s.Id).Order("id desc") @@ -41,6 +43,7 @@ func (s *Service) Hits() ([]*types.Hit, error) { return hits, err.Error } +// LimitedHits returns the last 1024 successful/online 'hit' records for a service func (s *Service) LimitedHits() ([]*types.Hit, error) { var hits []*types.Hit 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 } +// reverseHits will reverse the service's hit slice func reverseHits(input []*types.Hit) []*types.Hit { if len(input) == 0 { return input @@ -55,6 +59,7 @@ func reverseHits(input []*types.Hit) []*types.Hit { 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) { var hits []*types.Hit col := hitsDB().Where("service = ?", s.Id) @@ -62,6 +67,7 @@ func (s *Service) SelectHitsGroupBy(group string) ([]*types.Hit, error) { return hits, err.Error } +// TotalHits returns the total amount of successfull hits a service has func (s *Service) TotalHits() (uint64, error) { var count uint64 col := hitsDB().Where("service = ?", s.Id) @@ -69,6 +75,7 @@ func (s *Service) TotalHits() (uint64, 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) { var count uint64 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 } +// Sum returns the added value Latency for all of the services successful hits. func (s *Service) Sum() (float64, error) { var amount float64 hits, err := s.Hits() diff --git a/core/services.go b/core/services.go index e348499c..bb3a2602 100644 --- a/core/services.go +++ b/core/services.go @@ -18,8 +18,10 @@ package core import ( "encoding/json" "fmt" + "github.com/hunterlong/statup/notifiers" "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" + "sort" "strconv" "time" ) @@ -32,6 +34,7 @@ func ReturnService(s *types.Service) *Service { return &Service{s} } +// SelectService returns a *core.Service from in memory func SelectService(id int64) *Service { for _, s := range CoreApp.Services { if s.(*Service).Id == id { @@ -41,6 +44,7 @@ func SelectService(id int64) *Service { 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) { var services []*Service db := servicesDB().Find(&services).Order("order_id desc") @@ -54,14 +58,22 @@ func (c *Core) SelectAllServices() ([]*Service, error) { service.AllFailures() CoreApp.Services = append(CoreApp.Services, service) } + reorderServices() 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 { data, _ := json.Marshal(s) return string(data) } +// AvgTime will return the average amount of time for a service to response back successfully func (s *Service) AvgTime() float64 { total, _ := s.TotalHits() if total == 0 { @@ -74,11 +86,13 @@ func (s *Service) AvgTime() float64 { return val } +// Online24 returns the service's uptime percent within last 24 hours func (s *Service) Online24() float32 { ago := time.Now().Add(-24 * time.Hour) 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 { failed, _ := s.TotalFailuresSince(ago) if failed == 0 { @@ -100,17 +114,20 @@ func (s *Service) OnlineSince(ago time.Time) float32 { return s.Online24Hours } +// DateScan struct is for creating the charts.js graph JSON array type DateScan struct { CreatedAt time.Time `json:"x"` Value int64 `json:"y"` } +// lastFailure returns the last failure a service had func (s *Service) lastFailure() *Failure { limited := s.LimitedFailures() last := limited[len(limited)-1] return last } +// SmallText returns a short description about a services status func (s *Service) SmallText() string { last := s.LimitedFailures() 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 { var sql string switch CoreApp.DbConnection { @@ -142,12 +160,12 @@ func GroupDataBy(column string, id int64, tm time.Time, increment string) string return sql } +// GraphData returns the JSON object used by Charts.js to render the chart func (s *Service) GraphData() string { var d []*DateScan since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0) sql := GroupDataBy("hits", s.Id, since, "minute") rows, err := DbSession.Raw(sql).Rows() - defer rows.Close() if err != nil { utils.Log(2, err) return "" @@ -175,11 +193,13 @@ func (s *Service) GraphData() string { return string(data) } +// AvgUptime24 returns a service's average online status for last 24 hours func (s *Service) AvgUptime24() string { ago := time.Now().Add(-24 * time.Hour) return s.AvgUptime(ago) } +// AvgUptime returns average online status for last 24 hours func (s *Service) AvgUptime(ago time.Time) string { failed, _ := s.TotalFailuresSince(ago) if failed == 0 { @@ -201,6 +221,7 @@ func (s *Service) AvgUptime(ago time.Time) string { return amount } +// TotalUptime returns the total uptime percent of a service func (s *Service) TotalUptime() string { hits, _ := s.TotalHits() failures, _ := s.TotalFailures() @@ -216,6 +237,7 @@ func (s *Service) TotalUptime() string { return amount } +// index returns a services index int for updating the []*core.Services slice func (s *Service) index() int { for k, service := range CoreApp.Services { if s.Id == service.(*Service).Id { @@ -225,11 +247,13 @@ func (s *Service) index() int { return 0 } +// updateService will update a service in the []*core.Services slice func updateService(service *Service) { index := service.index() 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 { i := u.index() err := servicesDB().Delete(u) @@ -240,14 +264,17 @@ func (u *Service) Delete() error { u.Close() slice := CoreApp.Services CoreApp.Services = append(slice[:i], slice[i+1:]...) - //OnDeletedService(u) + reorderServices() + notifiers.OnDeletedService(u.Service) return err.Error } +// UpdateSingle will update a single column for a service func (u *Service) UpdateSingle(attr ...interface{}) 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 { err := servicesDB().Update(u) if err.Error != nil { @@ -260,11 +287,13 @@ func (u *Service) Update(restart bool) error { u.SleepDuration = time.Duration(u.Interval) * time.Second go u.CheckQueue(true) } + reorderServices() updateService(u) - //OnUpdateService(u) + notifiers.OnUpdatedService(u.Service) return err.Error } +// Create will create a service and insert it into the database func (u *Service) Create() (int64, error) { u.CreatedAt = time.Now() db := servicesDB().Create(u) @@ -275,13 +304,17 @@ func (u *Service) Create() (int64, error) { u.Start() go u.CheckQueue(true) CoreApp.Services = append(CoreApp.Services, u) + reorderServices() + notifiers.OnNewService(u.Service) return u.Id, nil } +// ServicesCount returns the amount of services inside the []*core.Services slice func (c *Core) ServicesCount() int { return len(c.Services) } +// CountOnline func (c *Core) CountOnline() int { amount := 0 for _, s := range CoreApp.Services { diff --git a/core/setup.go b/core/setup.go index 6bf38244..f2b1c370 100644 --- a/core/setup.go +++ b/core/setup.go @@ -22,6 +22,7 @@ import ( "os" ) +// DeleteConfig will delete the 'config.yml' file func DeleteConfig() { err := os.Remove(utils.Directory + "/config.yml") if err != nil { @@ -33,6 +34,7 @@ type ErrorResponse struct { Error string } +// LoadSampleData will create the example/dummy services for a brand new Statup installation func LoadSampleData() error { utils.Log(1, "Inserting Sample Data...") s1 := ReturnService(&types.Service{ diff --git a/core/users.go b/core/users.go index 345e5321..0f3e506f 100644 --- a/core/users.go +++ b/core/users.go @@ -27,16 +27,19 @@ type User struct { *types.User } +// ReturnUser returns *core.User based off a *types.User func ReturnUser(u *types.User) *User { return &User{u} } +// SelectUser returns the User based on the user's ID. func SelectUser(id int64) (*User, error) { var user User err := usersDB().First(&user, id) return &user, err.Error } +// SelectUser returns the User based on the user's username func SelectUsername(username string) (*User, error) { var user User res := usersDB().Where("username = ?", username) @@ -44,15 +47,18 @@ func SelectUsername(username string) (*User, error) { return &user, err.Error } +// Delete will remove the user record from the database func (u *User) Delete() error { return usersDB().Delete(u).Error } +// Update will update the user's record in database func (u *User) Update() error { u.CreatedAt = time.Now() return usersDB().Update(u).Error } +// Create will insert a new user into the database func (u *User) Create() (int64, error) { u.CreatedAt = time.Now() u.Password = utils.HashPassword(u.Password) @@ -69,6 +75,7 @@ func (u *User) Create() (int64, error) { return u.Id, db.Error } +// SelectAllUsers returns all users func SelectAllUsers() ([]*User, error) { var users []*User db := usersDB().Find(&users) @@ -78,6 +85,8 @@ func SelectAllUsers() ([]*User, 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) { user, err := SelectUsername(username) if err != nil { @@ -90,6 +99,7 @@ func AuthUser(username, password string) (*User, bool) { return nil, false } +// CheckHash returns true if the password matches with a hashed bcrypt password func CheckHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil diff --git a/notifiers/events.go b/notifiers/events.go index 730b2568..192ab0ee 100644 --- a/notifiers/events.go +++ b/notifiers/events.go @@ -17,7 +17,7 @@ package notifiers import "github.com/hunterlong/statup/types" -// Notifier interface +// OnSave will trigger a notifier when it has been saved - Notifier interface func OnSave(method string) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { 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) { for _, comm := range AllCommunications { if IsType(comm, "CoreEvents") { diff --git a/notifiers/interfaces.go b/notifiers/interfaces.go index 00038e6a..54f6b540 100644 --- a/notifiers/interfaces.go +++ b/notifiers/interfaces.go @@ -19,17 +19,25 @@ import "github.com/hunterlong/statup/types" // Notifier interface is required to create a new Notifier type Notifier interface { + // Init will load and install the notifier if needed Init() error + // Install will install the notifier into the database Install() error + // Run will trigger inside of the notifier when enabled Run() error + // OnSave is triggered when the notifier is saved OnSave() error + // Test will run a function inside the notifier to Test if it works Test() error + // Select returns the *Notification for a notifier 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 { + // OnSuccess is triggered when a service is successful OnSuccess(*types.Service) + // OnFailure is triggered when a service is failing OnFailure(*types.Service, *types.Failure) } diff --git a/source/source.go b/source/source.go index 8fb2c049..54322393 100644 --- a/source/source.go +++ b/source/source.go @@ -27,12 +27,13 @@ import ( ) var ( - CssBox *rice.Box - ScssBox *rice.Box - JsBox *rice.Box - TmplBox *rice.Box + CssBox *rice.Box // CSS files from the 'source/css' directory, this will be loaded into '/assets/css' + ScssBox *rice.Box // SCSS files from the 'source/scss' directory, this will be loaded into '/assets/scss' + JsBox *rice.Box // JS files from the 'source/js' directory, this will be loaded into '/assets/js' + 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() { CssBox = rice.MustFindBox("css") ScssBox = rice.MustFindBox("scss") @@ -40,6 +41,7 @@ func Assets() { TmplBox = rice.MustFindBox("tmpl") } +// CompileSASS will attempt to compile the SASS files into CSS func CompileSASS(folder string) error { sassBin := os.Getenv("SASS") if sassBin == "" { @@ -93,6 +95,7 @@ func CompileSASS(folder string) error { return err } +// UsingAssets returns true if the '/assets' folder is found in the directory func UsingAssets(folder string) bool { if _, err := os.Stat(folder + "/assets"); err == nil { return true @@ -112,6 +115,7 @@ func UsingAssets(folder string) bool { return false } +// SaveAsset will save an asset to the '/assets/' folder. func SaveAsset(data []byte, folder, file string) error { utils.Log(1, fmt.Sprintf("Saving %v/%v into assets folder", folder, file)) err := ioutil.WriteFile(folder+"/assets/"+file, data, 0744) @@ -122,6 +126,7 @@ func SaveAsset(data []byte, folder, file string) error { return nil } +// OpenAsset returns a file's contents as a string func OpenAsset(folder, file string) string { dat, err := ioutil.ReadFile(folder + "/assets/" + file) if err != nil { @@ -131,6 +136,7 @@ func OpenAsset(folder, file string) string { return string(dat) } +// CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory func CreateAllAssets(folder string) error { utils.Log(1, fmt.Sprintf("Dump Statup assets into %v/assets", folder)) MakePublicFolder(folder + "/assets") @@ -156,6 +162,7 @@ func CreateAllAssets(folder string) error { return err } +// DeleteAllAssets will delete the '/assets' folder func DeleteAllAssets(folder string) error { err := os.RemoveAll(folder + "/assets") if err != nil { @@ -166,6 +173,7 @@ func DeleteAllAssets(folder string) error { return err } +// CopyToPublic will create a file from a rice Box to the '/assets' directory func CopyToPublic(box *rice.Box, folder, file string) error { assetFolder := fmt.Sprintf("%v/%v", folder, file) 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 } +// MakePublicFolder will create a new folder func MakePublicFolder(folder string) error { utils.Log(1, fmt.Sprintf("Creating folder '%v'", folder)) if _, err := os.Stat(folder); os.IsNotExist(err) { @@ -194,6 +203,7 @@ func MakePublicFolder(folder string) error { return nil } +// copyAndCapture captures the response from a terminal command func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) { var out []byte buf := make([]byte, 1024, 1024) diff --git a/types/core.go b/types/core.go index c34ba999..d8964d75 100644 --- a/types/core.go +++ b/types/core.go @@ -31,12 +31,6 @@ type Core struct { 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 { SelectAllServices() ([]*Service, error) Count24HFailures() uint64 diff --git a/types/failure.go b/types/failure.go index 664dd572..99630ed8 100644 --- a/types/failure.go +++ b/types/failure.go @@ -14,6 +14,6 @@ type Failure struct { } type FailureInterface interface { - Ago() string - ParseError() string + Ago() string // Ago returns a human readble timestamp + ParseError() string // ParseError returns a human readable error for a service failure } diff --git a/types/service.go b/types/service.go index c62f2b21..b3c699c7 100644 --- a/types/service.go +++ b/types/service.go @@ -89,16 +89,19 @@ type ServiceInterface interface { AllCheckins() []*Checkin } +// Start will create a channel for the service checking go routine func (s *Service) Start() { s.Running = make(chan bool) } +// Close will stop the go routine that is checking if service is online or not func (s *Service) Close() { if s.IsRunning() { close(s.Running) } } +// IsRunning returns true if the service go routine is running func (s *Service) IsRunning() bool { if s.Running == nil { return false diff --git a/utils/encryption.go b/utils/encryption.go index f8e7ac35..a197222c 100644 --- a/utils/encryption.go +++ b/utils/encryption.go @@ -23,11 +23,13 @@ import ( "math/rand" ) +// HashPassword returns the bcrypt hash of a password string func HashPassword(password string) string { bytes, _ := bcrypt.GenerateFromPassword([]byte(password), 14) return string(bytes) } +// NewSHA1Hash returns a random SHA1 hash based on a specific length func NewSHA1Hash(n ...int) string { noRandomCharacters := 32 if len(n) > 0 { @@ -51,6 +53,7 @@ func RandomString(n int) string { return string(b) } +// Sha256 returns a SHA256 hash as string from []byte func Sha256(data []byte) string { h := sha1.New() h.Write(data) diff --git a/utils/log.go b/utils/log.go index d757a891..0fa11e1d 100644 --- a/utils/log.go +++ b/utils/log.go @@ -38,6 +38,7 @@ func Logger() *lumberjack.Logger { return ljLogger } +// createLog will create the '/logs' directory based on a directory func createLog(dir string) error { var err error _, err = os.Stat(dir + "/logs") @@ -56,6 +57,7 @@ func createLog(dir string) error { return err } +// InitLogs will create the '/logs' directory and creates a file '/logs/statup.log' for application logging func InitLogs() error { err := createLog(Directory) 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 { pushLastLine(err) var outErr error @@ -122,6 +125,7 @@ func Log(level int, err interface{}) error { return outErr } +// Http returns a log for a HTTP request func Http(r *http.Request) string { msg := fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host) 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 { LockLines.Lock() defer LockLines.Unlock() diff --git a/utils/utils.go b/utils/utils.go index e98afb57..a03feab6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -31,6 +31,7 @@ var ( Directory string ) +// init will set the utils.Directory to the current running directory, or STATUP_DIR if it is set func init() { if 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 { num, _ := strconv.Atoi(s) return int64(num) } +// IntString converts a int to a string func IntString(s int) string { return strconv.Itoa(s) } +// dir returns the current working directory func dir() string { dir, err := os.Getwd() if err != nil { @@ -57,16 +61,17 @@ func dir() string { } type Timestamp time.Time - type Timestamper interface { Ago() string } +// Ago returns a human readable timestamp based on the Timestamp (time.Time) interface func (t Timestamp) Ago() string { got, _ := timeago.TimeAgoWithTime(time.Now(), time.Time(t)) return got } +// UnderScoreString will return a string that replaces spaces and other characters to underscores func UnderScoreString(str string) string { // convert every letter to lower case @@ -90,6 +95,7 @@ func UnderScoreString(str string) string { return newStr } +// FileExists returns true if a file exists func FileExists(name string) bool { if _, err := os.Stat(name); err != nil { if os.IsNotExist(err) { @@ -99,6 +105,7 @@ func FileExists(name string) bool { return true } +// DeleteFile will attempt to delete a file func DeleteFile(file string) error { Log(1, "deleting file: "+file) err := os.Remove(file) @@ -108,10 +115,12 @@ func DeleteFile(file string) error { return nil } +// DeleteDirectory will attempt to delete a directory and all contents inside func DeleteDirectory(directory string) error { 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) { Log(1, "running command: "+cmd) testCmd := exec.Command("sh", "-c", cmd) @@ -142,6 +151,7 @@ func Command(cmd string) (string, string, error) { return outStr, errStr, err } +// copyAndCapture will read a terminal command into bytes func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) { var out []byte buf := make([]byte, 1024, 1024)