better logging (logrus), verbose (-v) mode, index page show create service btn, install.sh script, Dockerfile go version to 1.13, UI fixes

pull/335/head
hunterlong 2019-12-28 01:01:07 -08:00
parent b534652064
commit 15063734a5
62 changed files with 1016 additions and 616 deletions

View File

@ -1,12 +1,11 @@
FROM golang:1.12-alpine as base FROM golang:1.13-alpine as base
LABEL maintainer="Hunter Long (https://github.com/hunterlong)" LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
ARG VERSION ARG VERSION
ENV DEP_VERSION v0.5.0
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \ RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
chmod +x /usr/local/bin/sass chmod +x /usr/local/bin/sass
WORKDIR /go/src/github.com/hunterlong/statping WORKDIR /go/src/github.com/hunterlong/statping
ADD Makefile Gopkg.* /go/src/github.com/hunterlong/statping/ ADD Makefile go.mod /go/src/github.com/hunterlong/statping/
RUN go mod vendor && \ RUN go mod vendor && \
make dev-deps make dev-deps
ADD . /go/src/github.com/hunterlong/statping ADD . /go/src/github.com/hunterlong/statping

View File

@ -1,14 +1,14 @@
VERSION=$(shell cat version.txt) VERSION=$(shell cat version.txt)
SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278 SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278
BINARY_NAME=statping BINARY_NAME=statping
GOPATH:=$(GOPATH)
GOCMD=go GOCMD=go
GOBUILD=$(GOCMD) build -a GOBUILD=$(GOCMD) build -a
GOTEST=$(GOCMD) test GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get GOGET=$(GOCMD) get
GOVERSION=1.13.1 GOVERSION=1.13.1
GOINSTALL=$(GOCMD) install GOINSTALL=$(GOCMD) install
XGO=GOPATH=$(GOPATH) xgo -go $(GOVERSION) --dest=build GOPATH:=$(GOPATH)
XGO=$(GOPATH) xgo -go $(GOVERSION) --dest=build
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)"
RICE=$(GOPATH)/bin/rice RICE=$(GOPATH)/bin/rice
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH) PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)

View File

@ -43,9 +43,9 @@ func catchCLI(args []string) error {
switch args[0] { switch args[0] {
case "version": case "version":
if COMMIT != "" { if COMMIT != "" {
fmt.Printf("Statping v%v (%v)\n", VERSION, COMMIT) fmt.Printf("%v (%v)\n", VERSION, COMMIT)
} else { } else {
fmt.Printf("Statping v%v\n", VERSION) fmt.Printf("%v\n", VERSION)
} }
return errors.New("end") return errors.New("end")
case "assets": case "assets":
@ -60,18 +60,7 @@ func catchCLI(args []string) error {
} }
return errors.New("end") return errors.New("end")
case "update": case "update":
var err error return updateDisplay()
var gitCurrent githubResponse
if gitCurrent, err = checkGithubUpdates(); err != nil {
return err
}
fmt.Printf("Statping Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName)
if VERSION != gitCurrent.TagName[1:] {
fmt.Printf("You don't have the latest version v%v!\nDownload the latest release at: https://github.com/hunterlong/statping\n", gitCurrent.TagName[1:])
} else {
fmt.Printf("You have the latest version of Statping!\n")
}
return errors.New("end")
case "test": case "test":
cmd := args[1] cmd := args[1]
switch cmd { switch cmd {
@ -84,16 +73,16 @@ func catchCLI(args []string) error {
fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION) fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION)
utils.InitLogs() utils.InitLogs()
if core.Configs, err = core.LoadConfigFile(dir); err != nil { if core.Configs, err = core.LoadConfigFile(dir); err != nil {
utils.Log(4, "config.yml file not found") utils.Log.Errorln("config.yml file not found")
return err return err
} }
indexSource := ExportIndexHTML() indexSource := ExportIndexHTML()
//core.CloseDB() //core.CloseDB()
if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil { if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
utils.Log(4, err) utils.Log.Errorln(err)
return err return err
} }
utils.Log(1, "Exported Statping index page: 'index.html'") utils.Log.Infoln("Exported Statping index page: 'index.html'")
case "help": case "help":
HelpEcho() HelpEcho()
return errors.New("end") return errors.New("end")
@ -133,8 +122,8 @@ func catchCLI(args []string) error {
} }
return errors.New("end") return errors.New("end")
case "run": case "run":
utils.Log(1, "Running 1 time and saving to database...") utils.Log.Infoln("Running 1 time and saving to database...")
RunOnce() runOnce()
//core.CloseDB() //core.CloseDB()
fmt.Println("Check is complete.") fmt.Println("Check is complete.")
return errors.New("end") return errors.New("end")
@ -142,7 +131,7 @@ func catchCLI(args []string) error {
fmt.Println("Statping Environment Variable") fmt.Println("Statping Environment Variable")
envs, err := godotenv.Read(".env") envs, err := godotenv.Read(".env")
if err != nil { if err != nil {
utils.Log(4, "No .env file found in current directory.") utils.Log.Errorln("No .env file found in current directory.")
return err return err
} }
for k, e := range envs { for k, e := range envs {
@ -170,16 +159,33 @@ func ExportIndexHTML() []byte {
return w.Body.Bytes() return w.Body.Bytes()
} }
// RunOnce will initialize the Statping application and check each service 1 time, will not run HTTP server func updateDisplay() error {
func RunOnce() { var err error
var gitCurrent githubResponse
if gitCurrent, err = checkGithubUpdates(); err != nil {
return err
}
fmt.Printf("Statping Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName)
if VERSION != gitCurrent.TagName[1:] {
fmt.Printf("\n New Update %v Available\n", gitCurrent.TagName[1:])
fmt.Printf("Update Command:\n")
fmt.Printf("curl -o- -L https://statping.com/install.sh | bash\n\n")
} else {
fmt.Printf("You have the latest version of Statping!\n")
}
return errors.New("end")
}
// runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
func runOnce() {
var err error var err error
core.Configs, err = core.LoadConfigFile(utils.Directory) core.Configs, err = core.LoadConfigFile(utils.Directory)
if err != nil { if err != nil {
utils.Log(4, "config.yml file not found") utils.Log.Errorln("config.yml file not found")
} }
err = core.Configs.Connect(false, utils.Directory) err = core.Configs.Connect(false, utils.Directory)
if err != nil { if err != nil {
utils.Log(4, err) utils.Log.Errorln(err)
} }
core.CoreApp, err = core.SelectCore() core.CoreApp, err = core.SelectCore()
if err != nil { if err != nil {
@ -187,7 +193,7 @@ func RunOnce() {
} }
_, err = core.CoreApp.SelectAllServices(true) _, err = core.CoreApp.SelectAllServices(true)
if err != nil { if err != nil {
utils.Log(4, err) utils.Log.Errorln(err)
} }
for _, out := range core.CoreApp.Services { for _, out := range core.CoreApp.Services {
out.Check(true) out.Check(true)
@ -214,6 +220,8 @@ func HelpEcho() {
fmt.Printf("Flags:\n") fmt.Printf("Flags:\n")
fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)") fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)")
fmt.Println(" -port 8080 - Run HTTP server on Port (default: 8080)") fmt.Println(" -port 8080 - Run HTTP server on Port (default: 8080)")
fmt.Println(" -verbose 1 - Verbose mode levels 1 - 4 (default: 1)")
fmt.Println(" -env path/debug.env - Optional .env file to set as environment variables while running server")
fmt.Printf("Environment Variables:\n") fmt.Printf("Environment Variables:\n")
fmt.Println(" PORT - Set the outgoing port for the HTTP server (or use -port)") fmt.Println(" PORT - Set the outgoing port for the HTTP server (or use -port)")
fmt.Println(" IP - Bind a specific IP address to the HTTP server (or use -ip)") fmt.Println(" IP - Bind a specific IP address to the HTTP server (or use -ip)")
@ -241,7 +249,7 @@ func HelpEcho() {
func checkGithubUpdates() (githubResponse, error) { func checkGithubUpdates() (githubResponse, error) {
var gitResp githubResponse var gitResp githubResponse
url := "https://api.github.com/repos/hunterlong/statping/releases/latest" url := "https://api.github.com/repos/hunterlong/statping/releases/latest"
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(10*time.Second), true) contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true)
if err != nil { if err != nil {
return githubResponse{}, err return githubResponse{}, err
} }

View File

@ -52,7 +52,7 @@ func TestStartServerCommand(t *testing.T) {
func TestVersionCommand(t *testing.T) { func TestVersionCommand(t *testing.T) {
c := testcli.Command("statping", "version") c := testcli.Command("statping", "version")
c.Run() c.Run()
assert.True(t, c.StdoutContains("Statping v"+VERSION)) assert.True(t, c.StdoutContains(VERSION))
} }
func TestHelpCommand(t *testing.T) { func TestHelpCommand(t *testing.T) {
@ -90,7 +90,7 @@ func TestUpdateCommand(t *testing.T) {
commandAndSleep(cmd, time.Duration(15*time.Second), got) commandAndSleep(cmd, time.Duration(15*time.Second), got)
gg, _ := <-got gg, _ := <-got
t.Log(gg) t.Log(gg)
assert.Contains(t, gg, "Statping") assert.Contains(t, gg, VERSION)
} }
func TestAssetsCommand(t *testing.T) { func TestAssetsCommand(t *testing.T) {

View File

@ -16,13 +16,14 @@
package main package main
import ( import (
"github.com/hunterlong/statping/utils"
"flag" "flag"
"fmt" "fmt"
"github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/handlers" "github.com/hunterlong/statping/handlers"
"github.com/hunterlong/statping/plugin" "github.com/hunterlong/statping/plugin"
"github.com/hunterlong/statping/source" "github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"os" "os"
"os/signal" "os/signal"
@ -35,7 +36,8 @@ var (
// COMMIT stores the git commit hash for this version of Statping // COMMIT stores the git commit hash for this version of Statping
COMMIT string COMMIT string
ipAddress string ipAddress string
UsingDotEnv bool envFile string
verboseMode int
port int port int
) )
@ -47,17 +49,21 @@ func init() {
// -ip = 0.0.0.0 IP address for outgoing HTTP server // -ip = 0.0.0.0 IP address for outgoing HTTP server
// -port = 8080 Port number 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 Statping HTTP server") flag.StringVar(&ipAddress, "ip", "0.0.0.0", "IP address to run the Statping HTTP server")
p := flag.Int("port", 8080, "Port to run the HTTP server") flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server")
flag.IntVar(&port, "port", 8080, "Port to run the HTTP server")
flag.IntVar(&verboseMode, "verbose", 1, "Run in verbose mode to see detailed logs (1 - 4)")
flag.Parse() flag.Parse()
ipAddress = *ip
port = *p
if os.Getenv("PORT") != "" { if os.Getenv("PORT") != "" {
port = int(utils.ToInt(os.Getenv("PORT"))) port = int(utils.ToInt(os.Getenv("PORT")))
} }
if os.Getenv("IP") != "" { if os.Getenv("IP") != "" {
ipAddress = os.Getenv("IP") ipAddress = os.Getenv("IP")
} }
if os.Getenv("VERBOSE") != "" {
verboseMode = int(utils.ToInt(os.Getenv("VERBOSE")))
}
} }
// main will run the Statping application // main will run the Statping application
@ -67,6 +73,7 @@ func main() {
parseFlags() parseFlags()
loadDotEnvs() loadDotEnvs()
source.Assets() source.Assets()
utils.VerboseMode = verboseMode
if err := utils.InitLogs(); err != nil { if err := utils.InitLogs(); err != nil {
fmt.Printf("Statping Log Error: \n %v\n", err) fmt.Printf("Statping Log Error: \n %v\n", err)
os.Exit(2) os.Exit(2)
@ -83,33 +90,39 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
utils.Log(1, fmt.Sprintf("Starting Statping v%v", VERSION)) utils.Log.Info(fmt.Sprintf("Starting Statping v%v", VERSION))
updateDisplay()
core.Configs, err = core.LoadConfigFile(utils.Directory) core.Configs, err = core.LoadConfigFile(utils.Directory)
if err != nil { if err != nil {
utils.Log(3, err) utils.Log.Errorln(err)
core.SetupMode = true core.SetupMode = true
utils.Log(1, handlers.RunHTTPServer(ipAddress, port)) utils.Log.Infoln(handlers.RunHTTPServer(ipAddress, port))
os.Exit(1) os.Exit(1)
} }
mainProcess() mainProcess()
} }
// Close will gracefully stop the database connection, and log file
func Close() {
core.CloseDB()
utils.CloseLogs()
}
// sigterm will attempt to close the database connections gracefully // sigterm will attempt to close the database connections gracefully
func sigterm() { func sigterm() {
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
<-sigs <-sigs
core.CloseDB() Close()
os.Exit(1) os.Exit(1)
} }
// loadDotEnvs attempts to load database configs from a '.env' file in root directory // 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(envFile)
if err == nil { if err == nil {
utils.Log(1, "Environment file '.env' Loaded") utils.Log.Infoln("Environment file '.env' Loaded")
UsingDotEnv = true
} }
return err return err
} }
@ -120,7 +133,7 @@ func mainProcess() {
var err error var err error
err = core.Configs.Connect(false, dir) err = core.Configs.Connect(false, dir)
if err != nil { if err != nil {
utils.Log(4, fmt.Sprintf("could not connect to database: %v", err)) utils.Log.Errorln(fmt.Sprintf("could not connect to database: %v", err))
} }
core.Configs.MigrateDatabase() core.Configs.MigrateDatabase()
core.InitApp() core.InitApp()

View File

@ -32,7 +32,7 @@ import (
// checkServices will start the checking go routine for each service // 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))) log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
for _, ser := range CoreApp.Services { for _, ser := range CoreApp.Services {
//go obj.StartCheckins() //go obj.StartCheckins()
go ser.CheckQueue(true) go ser.CheckQueue(true)
@ -60,7 +60,7 @@ CheckLoop:
for { for {
select { select {
case <-s.Running: case <-s.Running:
utils.Log(1, fmt.Sprintf("Stopping service: %v", s.Name)) log.Infoln(fmt.Sprintf("Stopping service: %v", s.Name))
break CheckLoop break CheckLoop
case <-time.After(s.SleepDuration): case <-time.After(s.SleepDuration):
s.Check(record) s.Check(record)
@ -229,7 +229,7 @@ func (s *Service) checkHttp(record bool) *Service {
if s.Expected.String != "" { if s.Expected.String != "" {
match, err := regexp.MatchString(s.Expected.String, string(content)) match, err := regexp.MatchString(s.Expected.String, string(content))
if err != nil { if err != nil {
utils.Log(2, fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String)) log.Warnln(fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String))
} }
if !match { if !match {
if record { if record {
@ -259,8 +259,8 @@ func recordSuccess(s *Service) {
PingTime: s.PingTime, PingTime: s.PingTime,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
utils.Log(1, fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
s.CreateHit(hit) s.CreateHit(hit)
log.WithFields(utils.ToFields(hit, s.Select())).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
notifier.OnSuccess(s.Service) notifier.OnSuccess(s.Service)
s.Online = true s.Online = true
s.SuccessNotified = true s.SuccessNotified = true
@ -268,18 +268,18 @@ func recordSuccess(s *Service) {
// recordFailure will create a new 'Failure' record in the database for a offline 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) {
fail := &Failure{&types.Failure{ fail := &types.Failure{
Service: s.Id, Service: s.Id,
Issue: issue, Issue: issue,
PingTime: s.PingTime, PingTime: s.PingTime,
CreatedAt: time.Now(), CreatedAt: time.Now(),
ErrorCode: s.LastStatusCode, ErrorCode: s.LastStatusCode,
}} }
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000)) log.WithFields(utils.ToFields(fail, s.Select())).Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
s.CreateFailure(fail) s.CreateFailure(fail)
s.Online = false s.Online = false
s.SuccessNotified = false s.SuccessNotified = false
s.UpdateNotify = CoreApp.UpdateNotify.Bool s.UpdateNotify = CoreApp.UpdateNotify.Bool
s.DownText = s.DowntimeText() s.DownText = s.DowntimeText()
notifier.OnFailure(s.Service, fail.Failure) notifier.OnFailure(s.Service, fail)
} }

View File

@ -47,14 +47,14 @@ CheckinLoop:
for { for {
select { select {
case <-c.Running: case <-c.Running:
utils.Log(1, fmt.Sprintf("Stopping checkin routine: %v", c.Name)) log.Infoln(fmt.Sprintf("Stopping checkin routine: %v", c.Name))
c.Failing = false c.Failing = false
break CheckinLoop break CheckinLoop
case <-time.After(reCheck): case <-time.After(reCheck):
utils.Log(1, fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period()))) log.Infoln(fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
if c.Expected().Seconds() <= 0 { if c.Expected().Seconds() <= 0 {
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt) issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt)
utils.Log(3, issue) log.Errorln(issue)
c.CreateFailure() c.CreateFailure()
} }
reCheck = c.Period() reCheck = c.Period()
@ -143,7 +143,7 @@ func (c *Checkin) Grace() time.Duration {
// Expected returns the duration of when the serviec should receive a Checkin // Expected returns the duration of when the serviec should receive a Checkin
func (c *Checkin) Expected() time.Duration { func (c *Checkin) Expected() time.Duration {
last := c.Last().CreatedAt last := c.Last().CreatedAt
now := time.Now() now := utils.Now()
lastDir := now.Sub(last) lastDir := now.Sub(last)
sub := time.Duration(c.Period() - lastDir) sub := time.Duration(c.Period() - lastDir)
return sub return sub
@ -213,7 +213,7 @@ func (c *Checkin) Create() (int64, error) {
c.ApiKey = utils.RandomString(7) c.ApiKey = utils.RandomString(7)
row := checkinDB().Create(&c) row := checkinDB().Create(&c)
if row.Error != nil { if row.Error != nil {
utils.Log(2, row.Error) log.Warnln(row.Error)
return 0, row.Error return 0, row.Error
} }
service := SelectService(c.ServiceId) service := SelectService(c.ServiceId)
@ -227,7 +227,7 @@ func (c *Checkin) Create() (int64, error) {
func (c *Checkin) Update() (int64, error) { func (c *Checkin) Update() (int64, error) {
row := checkinDB().Update(&c) row := checkinDB().Update(&c)
if row.Error != nil { if row.Error != nil {
utils.Log(2, row.Error) log.Warnln(row.Error)
return 0, row.Error return 0, row.Error
} }
return c.Id, row.Error return c.Id, row.Error
@ -236,11 +236,11 @@ func (c *Checkin) Update() (int64, error) {
// Create will create a new successful checkinHit // Create will create a new successful checkinHit
func (c *CheckinHit) Create() (int64, error) { func (c *CheckinHit) Create() (int64, error) {
if c.CreatedAt.IsZero() { if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now() c.CreatedAt = utils.Now()
} }
row := checkinHitsDB().Create(&c) row := checkinHitsDB().Create(&c)
if row.Error != nil { if row.Error != nil {
utils.Log(2, row.Error) log.Warnln(row.Error)
return 0, row.Error return 0, row.Error
} }
return c.Id, row.Error return c.Id, row.Error
@ -248,13 +248,13 @@ func (c *CheckinHit) Create() (int64, error) {
// Ago returns the duration of time between now and the last successful checkinHit // Ago returns the duration of time between now and the last successful checkinHit
func (c *CheckinHit) Ago() string { func (c *CheckinHit) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), c.CreatedAt) got, _ := timeago.TimeAgoWithTime(utils.Now(), c.CreatedAt)
return got return got
} }
// RecheckCheckinFailure will check if a Service Checkin has been reported yet // RecheckCheckinFailure will check if a Service Checkin has been reported yet
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) { func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
between := time.Now().Sub(time.Now()).Seconds() between := utils.Now().Sub(utils.Now()).Seconds()
if between > float64(c.Interval) { if between > float64(c.Interval) {
fmt.Println("rechecking every 15 seconds!") fmt.Println("rechecking every 15 seconds!")
time.Sleep(15 * time.Second) time.Sleep(15 * time.Second)

View File

@ -34,7 +34,7 @@ type ErrorResponse struct {
func LoadConfigFile(directory string) (*DbConfig, error) { func LoadConfigFile(directory string) (*DbConfig, error) {
var configs *DbConfig var configs *DbConfig
if os.Getenv("DB_CONN") != "" { if os.Getenv("DB_CONN") != "" {
utils.Log(1, "DB_CONN environment variable was found, waiting for database...") log.Infoln("DB_CONN environment variable was found, waiting for database...")
return LoadUsingEnv() return LoadUsingEnv()
} }
file, err := ioutil.ReadFile(directory + "/config.yml") file, err := ioutil.ReadFile(directory + "/config.yml")
@ -66,18 +66,18 @@ func LoadUsingEnv() (*DbConfig, error) {
err = Configs.Connect(true, utils.Directory) err = Configs.Connect(true, utils.Directory)
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
return nil, err return nil, err
} }
Configs.Save() Configs.Save()
exists := DbSession.HasTable("core") exists := DbSession.HasTable("core")
if !exists { if !exists {
utils.Log(1, fmt.Sprintf("Core database does not exist, creating now!")) log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
Configs.DropDatabase() Configs.DropDatabase()
Configs.CreateDatabase() Configs.CreateDatabase()
CoreApp, err = Configs.InsertCore() CoreApp, err = Configs.InsertCore()
if err != nil { if err != nil {
utils.Log(3, err) log.Errorln(err)
} }
username := os.Getenv("ADMIN_USER") username := os.Getenv("ADMIN_USER")
@ -195,7 +195,7 @@ func SampleData() error {
func DeleteConfig() error { func DeleteConfig() error {
err := os.Remove(utils.Directory + "/config.yml") err := os.Remove(utils.Directory + "/config.yml")
if err != nil { if err != nil {
utils.Log(3, err) log.Errorln(err)
return err return err
} }
return nil return nil

View File

@ -40,6 +40,7 @@ var (
CoreApp *Core // CoreApp is a global variable that contains many elements CoreApp *Core // CoreApp is a global variable that contains many elements
SetupMode bool // SetupMode will be true if Statping does not have a database connection SetupMode bool // SetupMode will be true if Statping does not have a database connection
VERSION string // VERSION is set on build automatically by setting a -ldflag VERSION string // VERSION is set on build automatically by setting a -ldflag
log = utils.Log.WithField("type", "core")
) )
func init() { func init() {

View File

@ -134,6 +134,7 @@ func TestEnvToConfig(t *testing.T) {
os.Setenv("DESCRIPTION", "Testing Statping") os.Setenv("DESCRIPTION", "Testing Statping")
os.Setenv("ADMIN_USER", "admin") os.Setenv("ADMIN_USER", "admin")
os.Setenv("ADMIN_PASS", "admin123") os.Setenv("ADMIN_PASS", "admin123")
os.Setenv("VERBOSE", "true")
config, err := EnvToConfig() config, err := EnvToConfig()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, config.DbConn, "sqlite") assert.Equal(t, config.DbConn, "sqlite")

View File

@ -217,7 +217,7 @@ func (db *DbConfig) Connect(retry bool, location string) error {
dbSession, err := gorm.Open(dbType, conn) dbSession, err := gorm.Open(dbType, conn)
if err != nil { if err != nil {
if retry { if retry {
utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", Configs.DbHost)) log.Infoln(fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", Configs.DbHost))
return db.waitForDb() return db.waitForDb()
} else { } else {
return err return err
@ -229,7 +229,10 @@ func (db *DbConfig) Connect(retry bool, location string) error {
err = dbSession.DB().Ping() err = dbSession.DB().Ping()
if err == nil { if err == nil {
DbSession = dbSession DbSession = dbSession
utils.Log(1, fmt.Sprintf("Database %v connection was successful.", dbType)) if utils.VerboseMode >= 4 {
DbSession.LogMode(true).Debug().SetLogger(log)
}
log.Infoln(fmt.Sprintf("Database %v connection was successful.", dbType))
} }
return err return err
} }
@ -244,7 +247,7 @@ func (db *DbConfig) waitForDb() error {
// this function is currently set to delete records 7+ days old every 60 minutes // this function is currently set to delete records 7+ days old every 60 minutes
func DatabaseMaintence() { func DatabaseMaintence() {
for range time.Tick(60 * time.Minute) { for range time.Tick(60 * time.Minute) {
utils.Log(1, "Checking for database records older than 3 months...") log.Infoln("Checking for database records older than 3 months...")
since := time.Now().AddDate(0, -3, 0).UTC() since := time.Now().AddDate(0, -3, 0).UTC()
DeleteAllSince("failures", since) DeleteAllSince("failures", since)
DeleteAllSince("hits", since) DeleteAllSince("hits", since)
@ -256,7 +259,7 @@ 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.Exec(sql) db := DbSession.Exec(sql)
if db.Error != nil { if db.Error != nil {
utils.Log(2, db.Error) log.Warnln(db.Error)
} }
} }
@ -265,12 +268,12 @@ func (db *DbConfig) Update() error {
var err error var err error
config, err := os.Create(utils.Directory + "/config.yml") config, err := os.Create(utils.Directory + "/config.yml")
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
return err return err
} }
data, err := yaml.Marshal(db) data, err := yaml.Marshal(db)
if err != nil { if err != nil {
utils.Log(3, err) log.Errorln(err)
return err return err
} }
config.WriteString(string(data)) config.WriteString(string(data))
@ -283,14 +286,14 @@ func (db *DbConfig) Save() (*DbConfig, error) {
var err error var err error
config, err := os.Create(utils.Directory + "/config.yml") config, err := os.Create(utils.Directory + "/config.yml")
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
return nil, err return nil, err
} }
db.ApiKey = utils.NewSHA1Hash(16) db.ApiKey = utils.NewSHA1Hash(16)
db.ApiSecret = utils.NewSHA1Hash(16) db.ApiSecret = utils.NewSHA1Hash(16)
data, err := yaml.Marshal(db) data, err := yaml.Marshal(db)
if err != nil { if err != nil {
utils.Log(3, err) log.Errorln(err)
return nil, err return nil, err
} }
config.WriteString(string(data)) config.WriteString(string(data))
@ -315,14 +318,14 @@ func (c *DbConfig) CreateCore() *Core {
} }
CoreApp, err := SelectCore() CoreApp, err := SelectCore()
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
} }
return CoreApp return CoreApp
} }
// DropDatabase will DROP each table Statping created // DropDatabase will DROP each table Statping created
func (db *DbConfig) DropDatabase() error { func (db *DbConfig) DropDatabase() error {
utils.Log(1, "Dropping Database Tables...") log.Infoln("Dropping Database Tables...")
err := DbSession.DropTableIfExists("checkins") err := DbSession.DropTableIfExists("checkins")
err = DbSession.DropTableIfExists("checkin_hits") err = DbSession.DropTableIfExists("checkin_hits")
err = DbSession.DropTableIfExists("notifications") err = DbSession.DropTableIfExists("notifications")
@ -340,7 +343,7 @@ func (db *DbConfig) DropDatabase() error {
// CreateDatabase will CREATE TABLES for each of the Statping elements // CreateDatabase will CREATE TABLES for each of the Statping elements
func (db *DbConfig) CreateDatabase() error { func (db *DbConfig) CreateDatabase() error {
var err error var err error
utils.Log(1, "Creating Database Tables...") log.Infoln("Creating Database Tables...")
for _, table := range DbModels { for _, table := range DbModels {
if err := DbSession.CreateTable(table); err.Error != nil { if err := DbSession.CreateTable(table); err.Error != nil {
return err.Error return err.Error
@ -349,7 +352,7 @@ func (db *DbConfig) CreateDatabase() error {
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error != nil { if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error != nil {
return err.Error return err.Error
} }
utils.Log(1, "Statping Database Created") log.Infoln("Statping Database Created")
return err return err
} }
@ -357,7 +360,7 @@ func (db *DbConfig) CreateDatabase() error {
// This function will NOT remove previous records, tables or columns from the database. // This function will NOT remove previous records, tables or columns from the database.
// If this function has an issue, it will ROLLBACK to the previous state. // If this function has an issue, it will ROLLBACK to the previous state.
func (db *DbConfig) MigrateDatabase() error { func (db *DbConfig) MigrateDatabase() error {
utils.Log(1, "Migrating Database Tables...") log.Infoln("Migrating Database Tables...")
tx := DbSession.Begin() tx := DbSession.Begin()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -372,9 +375,9 @@ func (db *DbConfig) MigrateDatabase() error {
} }
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error != nil { if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error != nil {
tx.Rollback() tx.Rollback()
utils.Log(3, fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error)) log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
return tx.Error return tx.Error
} }
utils.Log(1, "Statping Database Migrated") log.Infoln("Statping Database Migrated")
return tx.Commit().Error return tx.Commit().Error
} }

View File

@ -20,7 +20,6 @@ import (
"encoding/json" "encoding/json"
"github.com/hunterlong/statping/source" "github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"html/template" "html/template"
) )
@ -28,7 +27,7 @@ import (
func ExportChartsJs() string { func ExportChartsJs() string {
render, err := source.JsBox.String("charts.js") render, err := source.JsBox.String("charts.js")
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
} }
t := template.New("charts") t := template.New("charts")
t.Funcs(template.FuncMap{ t.Funcs(template.FuncMap{
@ -39,7 +38,7 @@ func ExportChartsJs() string {
t.Parse(render) t.Parse(render)
var tpl bytes.Buffer var tpl bytes.Buffer
if err := t.Execute(&tpl, CoreApp.Services); err != nil { if err := t.Execute(&tpl, CoreApp.Services); err != nil {
utils.Log(3, err) log.Errorln(err)
} }
result := tpl.String() result := tpl.String()
return result return result

View File

@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"github.com/ararog/timeago" "github.com/ararog/timeago"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -35,16 +34,15 @@ const (
) )
// CreateFailure will create a new Failure record for a service // CreateFailure will create a new Failure record for a service
func (s *Service) CreateFailure(fail types.FailureInterface) (int64, error) { func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
f := fail.(*Failure)
f.Service = s.Id f.Service = s.Id
row := failuresDB().Create(f) row := failuresDB().Create(f)
if row.Error != nil { if row.Error != nil {
utils.Log(3, row.Error) log.Errorln(row.Error)
return 0, row.Error return 0, row.Error
} }
sort.Sort(types.FailSort(s.Failures)) sort.Sort(types.FailSort(s.Failures))
s.Failures = append(s.Failures, f) //s.Failures = append(s.Failures, f)
if len(s.Failures) > limitedFailures { if len(s.Failures) > limitedFailures {
s.Failures = s.Failures[1:] s.Failures = s.Failures[1:]
} }
@ -57,7 +55,7 @@ func (s *Service) AllFailures() []*Failure {
col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc") col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc")
err := col.Find(&fails) err := col.Find(&fails)
if err.Error != nil { if err.Error != nil {
utils.Log(3, fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err)) log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
return nil return nil
} }
return fails return fails
@ -67,7 +65,7 @@ func (s *Service) AllFailures() []*Failure {
func (s *Service) DeleteFailures() { func (s *Service) DeleteFailures() {
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id) err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
if err.Error != nil { if err.Error != nil {
utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err)) log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err))
} }
s.Failures = nil s.Failures = nil
} }
@ -119,7 +117,7 @@ func CountFailures() uint64 {
var count uint64 var count uint64
err := failuresDB().Count(&count) err := failuresDB().Count(&count)
if err.Error != nil { if err.Error != nil {
utils.Log(2, err.Error) log.Warnln(err.Error)
return 0 return 0
} }
return count return count

View File

@ -17,7 +17,6 @@ package core
import ( import (
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"time" "time"
) )
@ -29,7 +28,7 @@ type Hit struct {
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 {
utils.Log(2, db.Error) log.Warnln(db.Error)
return 0, db.Error return 0, db.Error
} }
return h.Id, db.Error return h.Id, db.Error

View File

@ -18,7 +18,6 @@ package core
import ( import (
"fmt" "fmt"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"time" "time"
) )
@ -64,7 +63,7 @@ func (m *Message) Create() (int64, error) {
m.CreatedAt = time.Now().UTC() m.CreatedAt = time.Now().UTC()
db := messagesDb().Create(m) db := messagesDb().Create(m)
if db.Error != nil { if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error)) log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
return 0, db.Error return 0, db.Error
} }
return m.Id, nil return m.Id, nil
@ -80,7 +79,7 @@ func (m *Message) Delete() error {
func (m *Message) Update() (*Message, error) { func (m *Message) Update() (*Message, error) {
db := messagesDb().Update(m) db := messagesDb().Update(m)
if db.Error != nil { if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error)) log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
return nil, db.Error return nil, db.Error
} }
return m, nil return m, nil

View File

@ -54,7 +54,9 @@ sendMessages:
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) {
notifier := comm.(Notifier).Select() notifier := comm.(Notifier).Select()
utils.Log(1, fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name)) log.
WithField("trigger", "OnFailure").
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name))
comm.(BasicEvents).OnFailure(s, f) comm.(BasicEvents).OnFailure(s, f)
} }
} }
@ -74,7 +76,9 @@ func OnSuccess(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) { if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) {
notifier := comm.(Notifier).Select() notifier := comm.(Notifier).Select()
utils.Log(1, fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name)) log.
WithField("trigger", "OnSuccess").
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name))
comm.(BasicEvents).OnSuccess(s) comm.(BasicEvents).OnSuccess(s)
} }
} }
@ -84,7 +88,9 @@ func OnSuccess(s *types.Service) {
func OnNewService(s *types.Service) { func OnNewService(s *types.Service) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending new service notification for service %v", s.Name)) log.
WithField("trigger", "OnNewService").
Infoln(fmt.Sprintf("Sending new service notification for service %v", s.Name))
comm.(ServiceEvents).OnNewService(s) comm.(ServiceEvents).OnNewService(s)
} }
} }
@ -97,7 +103,7 @@ func OnUpdatedService(s *types.Service) {
} }
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated service notification for service %v", s.Name)) log.Infoln(fmt.Sprintf("Sending updated service notification for service %v", s.Name))
comm.(ServiceEvents).OnUpdatedService(s) comm.(ServiceEvents).OnUpdatedService(s)
} }
} }
@ -110,7 +116,7 @@ func OnDeletedService(s *types.Service) {
} }
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending deleted service notification for service %v", s.Name)) log.Infoln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
comm.(ServiceEvents).OnDeletedService(s) comm.(ServiceEvents).OnDeletedService(s)
} }
} }
@ -120,7 +126,7 @@ func OnDeletedService(s *types.Service) {
func OnNewUser(u *types.User) { func OnNewUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending new user notification for user %v", u.Username)) log.Infoln(fmt.Sprintf("Sending new user notification for user %v", u.Username))
comm.(UserEvents).OnNewUser(u) comm.(UserEvents).OnNewUser(u)
} }
} }
@ -130,7 +136,7 @@ func OnNewUser(u *types.User) {
func OnUpdatedUser(u *types.User) { func OnUpdatedUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated user notification for user %v", u.Username)) log.Infoln(fmt.Sprintf("Sending updated user notification for user %v", u.Username))
comm.(UserEvents).OnUpdatedUser(u) comm.(UserEvents).OnUpdatedUser(u)
} }
} }
@ -140,7 +146,7 @@ func OnUpdatedUser(u *types.User) {
func OnDeletedUser(u *types.User) { func OnDeletedUser(u *types.User) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending deleted user notification for user %v", u.Username)) log.Infoln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
comm.(UserEvents).OnDeletedUser(u) comm.(UserEvents).OnDeletedUser(u)
} }
} }
@ -150,7 +156,7 @@ func OnDeletedUser(u *types.User) {
func OnUpdatedCore(c *types.Core) { func OnUpdatedCore(c *types.Core) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated core notification")) log.Infoln(fmt.Sprintf("Sending updated core notification"))
comm.(CoreEvents).OnUpdatedCore(c) comm.(CoreEvents).OnUpdatedCore(c)
} }
} }
@ -178,7 +184,7 @@ func OnNewNotifier(n *Notification) {
func OnUpdatedNotifier(n *Notification) { func OnUpdatedNotifier(n *Notification) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated notifier for %v", n.Id)) log.Infoln(fmt.Sprintf("Sending updated notifier for %v", n.Id))
comm.(NotifierEvents).OnUpdatedNotifier(n) comm.(NotifierEvents).OnUpdatedNotifier(n)
} }
} }

View File

@ -233,7 +233,6 @@ func ExampleNotification_OnSuccess() {
example.AddQueue("example", msg) example.AddQueue("example", msg)
fmt.Println(len(example.Queue)) fmt.Println(len(example.Queue))
// Output: // Output:
// INFO: Notifier 'Example' added new item (example) to the queue. (1 queued)
// 1 // 1
} }
@ -270,7 +269,6 @@ func ExampleNotification_AddQueue() {
queue := example.Queue queue := example.Queue
fmt.Printf("Example has %v items in the queue", len(queue)) fmt.Printf("Example has %v items in the queue", len(queue))
// Output: // Output:
// INFO: Notifier 'Example' added new item (example) to the queue. (2 queued)
// Example has 2 items in the queue // Example has 2 items in the queue
} }

View File

@ -33,6 +33,7 @@ var (
// db holds the Statping database connection // db holds the Statping database connection
db *gorm.DB db *gorm.DB
timezone float32 timezone float32
log = utils.Log.WithField("type", "notifier")
) )
// Notification contains all the fields for a Statping Notifier. // Notification contains all the fields for a Statping Notifier.
@ -102,7 +103,7 @@ func (n *Notification) AfterFind() (err error) {
func (n *Notification) AddQueue(uid string, msg interface{}) { func (n *Notification) AddQueue(uid string, msg interface{}) {
data := &QueueData{uid, msg} data := &QueueData{uid, msg}
n.Queue = append(n.Queue, data) n.Queue = append(n.Queue, data)
utils.Log(1, fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) log.WithFields(utils.ToFields(data, n)).Infoln(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue)))
} }
// CanTest returns true if the notifier implements the OnTest interface // CanTest returns true if the notifier implements the OnTest interface
@ -169,8 +170,8 @@ func normalizeType(ty interface{}) string {
func (n *Notification) makeLog(msg interface{}) { func (n *Notification) makeLog(msg interface{}) {
log := &NotificationLog{ log := &NotificationLog{
Message: normalizeType(msg), Message: normalizeType(msg),
Time: utils.Timestamp(time.Now()), Time: utils.Timestamp(utils.Now()),
Timestamp: time.Now(), Timestamp: utils.Now(),
} }
n.logs = append(n.logs, log) n.logs = append(n.logs, log)
} }
@ -290,9 +291,9 @@ CheckNotifier:
msg := notification.Queue[0] msg := notification.Queue[0]
err := n.Send(msg.Data) err := n.Send(msg.Data)
if err != nil { if err != nil {
utils.Log(2, fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err)) log.WithFields(utils.ToFields(notification, msg)).Warnln(fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err))
} else { } else {
utils.Log(1, fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue))) log.WithFields(utils.ToFields(notification, msg)).Infoln(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue)))
} }
notification.makeLog(msg.Data) notification.makeLog(msg.Data)
if len(notification.Queue) > 1 { if len(notification.Queue) > 1 {
@ -311,10 +312,13 @@ CheckNotifier:
// install will check the database for the notification, if its not inserted it will insert a new record for it // install will check the database for the notification, if its not inserted it will insert a new record for it
func install(n Notifier) error { func install(n Notifier) error {
inDb := isInDatabase(n) inDb := isInDatabase(n)
log.WithField("installed", inDb).
WithFields(utils.ToFields(n)).
Debugln(fmt.Sprintf("Checking if notifier '%v' is installed: %v", n.Select().Method, inDb))
if !inDb { if !inDb {
_, err := insertDatabase(n) _, err := insertDatabase(n)
if err != nil { if err != nil {
utils.Log(3, err) log.Errorln(err)
return err return err
} }
} }
@ -333,13 +337,13 @@ func (n *Notification) LastSent() time.Duration {
// SentLastHour returns the total amount of notifications sent in last 1 hour // SentLastHour returns the total amount of notifications sent in last 1 hour
func (n *Notification) SentLastHour() int { func (n *Notification) SentLastHour() int {
since := time.Now().Add(-1 * time.Hour) since := utils.Now().Add(-1 * time.Hour)
return n.SentLast(since) return n.SentLast(since)
} }
// SentLastMinute returns the total amount of notifications sent in last 1 minute // SentLastMinute returns the total amount of notifications sent in last 1 minute
func (n *Notification) SentLastMinute() int { func (n *Notification) SentLastMinute() int {
since := time.Now().Add(-1 * time.Minute) since := utils.Now().Add(-1 * time.Minute)
return n.SentLast(since) return n.SentLast(since)
} }
@ -475,5 +479,5 @@ var ExampleService = &types.Service{
LastStatusCode: 404, LastStatusCode: 404,
Expected: types.NewNullString("test example"), Expected: types.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>", LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour), CreatedAt: utils.Now().Add(-24 * time.Hour),
} }

View File

@ -29,7 +29,7 @@ var (
// InsertSampleData will create the example/dummy services for a brand new Statping installation // InsertSampleData will create the example/dummy services for a brand new Statping installation
func InsertSampleData() error { func InsertSampleData() error {
utils.Log(1, "Inserting Sample Data...") log.Infoln("Inserting Sample Data...")
insertSampleGroups() insertSampleGroups()
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC() createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
@ -113,7 +113,7 @@ func InsertSampleData() error {
insertSampleIncidents() insertSampleIncidents()
utils.Log(1, "Sample data has finished importing") log.Infoln("Sample data has finished importing")
return nil return nil
} }
@ -211,7 +211,7 @@ func InsertSampleHits() error {
service := SelectService(i) service := SelectService(i)
seed := time.Now().UnixNano() seed := time.Now().UnixNano()
utils.Log(1, fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name)) log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
createdAt := sampleStart createdAt := sampleStart
p := utils.NewPerlin(2., 2., 10, seed) p := utils.NewPerlin(2., 2., 10, seed)
@ -455,17 +455,17 @@ func InsertLargeSampleData() error {
func insertFailureRecords(since time.Time, amount int64) { func insertFailureRecords(since time.Time, amount int64) {
for i := int64(14); i <= 15; i++ { for i := int64(14); i <= 15; i++ {
service := SelectService(i) service := SelectService(i)
utils.Log(1, fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name)) log.Infoln(fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
createdAt := since createdAt := since
for fi := int64(1); fi <= amount; fi++ { for fi := int64(1); fi <= amount; fi++ {
createdAt = createdAt.Add(2 * time.Minute) createdAt = createdAt.Add(2 * time.Minute)
failure := &Failure{&types.Failure{ failure := &types.Failure{
Service: service.Id, Service: service.Id,
Issue: "testing right here", Issue: "testing right here",
CreatedAt: createdAt, CreatedAt: createdAt,
}} }
service.CreateFailure(failure) service.CreateFailure(failure)
} }
@ -476,7 +476,7 @@ func insertFailureRecords(since time.Time, amount int64) {
func insertHitRecords(since time.Time, amount int64) { func insertHitRecords(since time.Time, amount int64) {
for i := int64(1); i <= 15; i++ { for i := int64(1); i <= 15; i++ {
service := SelectService(i) service := SelectService(i)
utils.Log(1, fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name)) log.Infoln(fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
createdAt := since createdAt := since
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano()) p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano())
for hi := int64(1); hi <= amount; hi++ { for hi := int64(1); hi <= amount; hi++ {

View File

@ -101,7 +101,7 @@ func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
var services []*Service var services []*Service
db := servicesDB().Find(&services).Order("order_id desc") db := servicesDB().Find(&services).Order("order_id desc")
if db.Error != nil { if db.Error != nil {
utils.Log(3, fmt.Sprintf("service error: %v", db.Error)) log.Errorln(fmt.Sprintf("service error: %v", db.Error))
return nil, db.Error return nil, db.Error
} }
CoreApp.Services = nil CoreApp.Services = nil
@ -272,7 +272,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
model = model.Order("timeframe asc", false).Group("timeframe") model = model.Order("timeframe asc", false).Group("timeframe")
rows, err := model.Rows() rows, err := model.Rows()
if err != nil { if err != nil {
utils.Log(3, fmt.Errorf("issue fetching service chart data: %v", err)) log.Errorln(fmt.Errorf("issue fetching service chart data: %v", err))
} }
for rows.Next() { for rows.Next() {
var gd DateScan var gd DateScan
@ -284,7 +284,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
if CoreApp.DbConnection == "postgres" { if CoreApp.DbConnection == "postgres" {
createdTime, err = time.Parse(types.TIME_NANO, createdAt) createdTime, err = time.Parse(types.TIME_NANO, createdAt)
if err != nil { if err != nil {
utils.Log(4, fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO)) log.Errorln(fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
} }
} else { } else {
createdTime, err = time.Parse(types.TIME, createdAt) createdTime, err = time.Parse(types.TIME, createdAt)
@ -301,7 +301,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
func (d *DateScanObj) ToString() string { func (d *DateScanObj) ToString() string {
data, err := json.Marshal(d.Array) data, err := json.Marshal(d.Array)
if err != nil { if err != nil {
utils.Log(2, err) log.Warnln(err)
return "{}" return "{}"
} }
return string(data) return string(data)
@ -371,7 +371,7 @@ func (s *Service) Delete() error {
i := s.index() i := s.index()
err := servicesDB().Delete(s) err := servicesDB().Delete(s)
if err.Error != nil { if err.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error)) log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
return err.Error return err.Error
} }
s.Close() s.Close()
@ -386,7 +386,7 @@ func (s *Service) Delete() error {
func (s *Service) Update(restart bool) error { func (s *Service) Update(restart bool) error {
err := servicesDB().Update(&s) err := servicesDB().Update(&s)
if err.Error != nil { if err.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", s.Name, err)) log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
return err.Error return err.Error
} }
// clear the notification queue for a service // clear the notification queue for a service
@ -413,7 +413,7 @@ func (s *Service) Create(check bool) (int64, error) {
s.CreatedAt = time.Now() s.CreatedAt = time.Now()
db := servicesDB().Create(s) db := servicesDB().Create(s)
if db.Error != nil { if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error)) log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
return 0, db.Error return 0, db.Error
} }
s.Start() s.Start()

View File

@ -17,6 +17,7 @@ package core
import ( import (
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
"time" "time"
@ -24,7 +25,6 @@ import (
var ( var (
newServiceId int64 newServiceId int64
newGroupId int64
) )
func TestSelectHTTPService(t *testing.T) { func TestSelectHTTPService(t *testing.T) {
@ -118,7 +118,7 @@ func TestCheckTCPService(t *testing.T) {
} }
func TestServiceOnline24Hours(t *testing.T) { func TestServiceOnline24Hours(t *testing.T) {
since := time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute) since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
service := SelectService(1) service := SelectService(1)
assert.Equal(t, float32(100), service.OnlineSince(since)) assert.Equal(t, float32(100), service.OnlineSince(since))
service2 := SelectService(5) service2 := SelectService(5)
@ -134,7 +134,7 @@ func TestServiceSmallText(t *testing.T) {
} }
func TestServiceAvgUptime(t *testing.T) { func TestServiceAvgUptime(t *testing.T) {
since := time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute) since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
service := SelectService(1) service := SelectService(1)
assert.NotEqual(t, "0.00", service.AvgUptime(since)) assert.NotEqual(t, "0.00", service.AvgUptime(since))
service2 := SelectService(5) service2 := SelectService(5)
@ -256,10 +256,10 @@ func TestServiceFailedTCPCheck(t *testing.T) {
} }
func TestCreateServiceFailure(t *testing.T) { func TestCreateServiceFailure(t *testing.T) {
fail := &Failure{&types.Failure{ fail := &types.Failure{
Issue: "This is not an issue, but it would container HTTP response errors.", Issue: "This is not an issue, but it would container HTTP response errors.",
Method: "http", Method: "http",
}} }
service := SelectService(8) service := SelectService(8)
id, err := service.CreateFailure(fail) id, err := service.CreateFailure(fail)
assert.Nil(t, err) assert.Nil(t, err)
@ -398,7 +398,7 @@ func TestService_TotalFailures24(t *testing.T) {
func TestService_TotalFailuresOnDate(t *testing.T) { func TestService_TotalFailuresOnDate(t *testing.T) {
t.SkipNow() t.SkipNow()
ago := time.Now().UTC() ago := utils.Now().UTC()
service := SelectService(8) service := SelectService(8)
failures, err := service.TotalFailuresOnDate(ago) failures, err := service.TotalFailuresOnDate(ago)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -77,7 +77,7 @@ func (u *User) Create() (int64, error) {
return 0, db.Error return 0, db.Error
} }
if u.Id == 0 { if u.Id == 0 {
utils.Log(3, fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error)) log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
return 0, db.Error return 0, db.Error
} }
return u.Id, db.Error return u.Id, db.Error
@ -88,7 +88,7 @@ func SelectAllUsers() ([]*User, error) {
var users []*User var users []*User
db := usersDB().Find(&users) db := usersDB().Find(&users)
if db.Error != nil { if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to load all users. %v", db.Error)) log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error))
return nil, db.Error return nil, db.Error
} }
return users, db.Error return users, db.Error
@ -99,7 +99,7 @@ func SelectAllUsers() ([]*User, error) {
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 {
utils.Log(2, fmt.Errorf("user %v not found", username)) log.Warnln(fmt.Errorf("user %v not found", username))
return nil, false return nil, false
} }
if CheckHash(password, user.Password) { if CheckHash(password, user.Password) {

240
dev/COVERAGE.html vendored

File diff suppressed because one or more lines are too long

4
dev/Dockerfile vendored
View File

@ -1,5 +1,5 @@
FROM golang:1.11-alpine as base FROM golang:1.13-alpine as base
MAINTAINER "Hunter Long (https://github.com/hunterlong)" LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
ARG VERSION ARG VERSION
ENV DEP_VERSION v0.5.0 ENV DEP_VERSION v0.5.0
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq

2
dev/README.md vendored
View File

@ -2496,7 +2496,7 @@ for application logging
```go ```go
func Log(level int, err interface{}) error func Log(level int, err interface{}) error
``` ```
Log creates a new entry in the Logger. Log has 1-5 levels depending on how Log creates a new entry in the utils.Log. Log has 1-5 levels depending on how
critical the log/error is critical the log/error is
#### func NewSHA1Hash #### func NewSHA1Hash

4
doc.go
View File

@ -1,4 +1,4 @@
// Package statping is a server monitoring application that includs a status page server. Visit the Statping repo at // Package statping is a server monitoring application that includes a status page server. Visit the Statping repo at
// https://github.com/hunterlong/statping to get a full understanding of what this application can do. // https://github.com/hunterlong/statping to get a full understanding of what this application can do.
// //
// Install Statping // Install Statping
@ -12,7 +12,7 @@
// brew install statping // brew install statping
// //
// // Linux installation // // Linux installation
// bash <(curl -s https://assets.statping.com/install.sh) // curl -o- -L https://statping.com/install.sh | bash
// statping version // statping version
// //
// Docker // Docker

2
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/agnivade/levenshtein v1.0.2 // indirect github.com/agnivade/levenshtein v1.0.2 // indirect
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d
github.com/daaku/go.zipexe v1.0.1 // indirect github.com/daaku/go.zipexe v1.0.1 // indirect
github.com/fatih/structs v1.1.0
github.com/go-mail/mail v2.3.1+incompatible github.com/go-mail/mail v2.3.1+incompatible
github.com/go-yaml/yaml v2.1.0+incompatible github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
@ -20,6 +21,7 @@ require (
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
github.com/russross/blackfriday/v2 v2.0.1 github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.2.0
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
github.com/vektah/gqlparser v1.1.2 github.com/vektah/gqlparser v1.1.2

4
go.sum
View File

@ -39,6 +39,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -95,6 +97,7 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -144,6 +147,7 @@ github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJ
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -58,7 +58,7 @@ func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
} }
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) { func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
utils.Log(2, fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error())) log.Warnln(fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
output := apiResponse{ output := apiResponse{
Status: "error", Status: "error",
Error: err.Error(), Error: err.Error(),

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"github.com/hunterlong/statping/utils"
"sync" "sync"
"time" "time"
) )
@ -25,7 +26,7 @@ func (item Item) Expired() bool {
if item.Expiration == 0 { if item.Expiration == 0 {
return false return false
} }
return time.Now().UnixNano() > item.Expiration return utils.Now().UnixNano() > item.Expiration
} }
//Storage mecanism for caching strings in memory //Storage mecanism for caching strings in memory
@ -68,6 +69,6 @@ func (s Storage) Set(key string, content []byte, duration time.Duration) {
defer s.mu.Unlock() defer s.mu.Unlock()
s.items[key] = Item{ s.items[key] = Item{
Content: content, Content: content,
Expiration: time.Now().Add(duration).UnixNano(), Expiration: utils.Now().Add(duration).UnixNano(),
} }
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"net" "net"
"net/http" "net/http"
"time"
) )
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) { func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
@ -81,7 +80,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
hit := &types.CheckinHit{ hit := &types.CheckinHit{
Checkin: checkin.Id, Checkin: checkin.Id,
From: ip, From: ip,
CreatedAt: time.Now().UTC(), CreatedAt: utils.Now().UTC(),
} }
checkinHit := core.ReturnCheckinHit(hit) checkinHit := core.ReturnCheckinHit(hit)
if checkin.Last() == nil { if checkin.Last() == nil {
@ -95,7 +94,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
checkin.Failing = false checkin.Failing = false
checkin.LastHit = utils.Timezoner(time.Now().UTC(), core.CoreApp.Timezone) checkin.LastHit = utils.Timezoner(utils.Now().UTC(), core.CoreApp.Timezone)
sendJsonAction(checkinHit, "update", w, r) sendJsonAction(checkinHit, "update", w, r)
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"net/http" "net/http"
"strconv" "strconv"
"time"
) )
func dashboardHandler(w http.ResponseWriter, r *http.Request) { func dashboardHandler(w http.ResponseWriter, r *http.Request) {
@ -50,7 +49,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
session.Values["user_id"] = user.Id session.Values["user_id"] = user.Id
session.Values["admin"] = user.Admin.Bool session.Values["admin"] = user.Admin.Bool
session.Save(r, w) session.Save(r, w)
utils.Log(1, fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr)) log.Infoln(fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
http.Redirect(w, r, "/dashboard", http.StatusSeeOther) http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else { } else {
err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."} err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."}
@ -115,6 +114,6 @@ func exportHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", strconv.Itoa(fileSize)) w.Header().Set("Content-Length", strconv.Itoa(fileSize))
w.Header().Set("Content-Control", "private, no-transform, no-store, must-revalidate") w.Header().Set("Content-Control", "private, no-transform, no-store, must-revalidate")
http.ServeContent(w, r, "export.json", time.Now(), bytes.NewReader(export)) http.ServeContent(w, r, "export.json", utils.Now(), bytes.NewReader(export))
} }

View File

@ -56,11 +56,11 @@ func RunHTTPServer(ip string, port int) error {
cert := utils.FileExists(utils.Directory + "/server.crt") cert := utils.FileExists(utils.Directory + "/server.crt")
if key && cert { if key && cert {
utils.Log(1, "server.cert and server.key was found in root directory! Starting in SSL mode.") log.Infoln("server.cert and server.key was found in root directory! Starting in SSL mode.")
utils.Log(1, fmt.Sprintf("Statping Secure HTTPS Server running on https://%v:%v", ip, 443)) log.Infoln(fmt.Sprintf("Statping Secure HTTPS Server running on https://%v:%v", ip, 443))
usingSSL = true usingSSL = true
} else { } else {
utils.Log(1, "Statping HTTP Server running on http://"+host) log.Infoln("Statping HTTP Server running on http://" + host)
} }
router = Router() router = Router()
@ -178,7 +178,7 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
mainTemplate.Funcs(handlerFuncs(w, r)) mainTemplate.Funcs(handlerFuncs(w, r))
mainTemplate, err = mainTemplate.Parse(mainTmpl) mainTemplate, err = mainTemplate.Parse(mainTmpl)
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
return err return err
} }
// render all templates // render all templates
@ -187,7 +187,7 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
tmp, _ := source.TmplBox.String(temp) tmp, _ := source.TmplBox.String(temp)
mainTemplate, err = mainTemplate.Parse(tmp) mainTemplate, err = mainTemplate.Parse(tmp)
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
return err return err
} }
} }
@ -196,7 +196,7 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
tmp, _ := source.JsBox.String(temp) tmp, _ := source.JsBox.String(temp)
mainTemplate, err = mainTemplate.Parse(tmp) mainTemplate, err = mainTemplate.Parse(tmp)
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
return err return err
} }
} }
@ -205,7 +205,6 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
// ExecuteResponse will render a HTTP response for the front end user // ExecuteResponse will render a HTTP response for the front end user
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) { func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) {
utils.Http(r)
if url, ok := redirect.(string); ok { if url, ok := redirect.(string); ok {
http.Redirect(w, r, url, http.StatusSeeOther) http.Redirect(w, r, url, http.StatusSeeOther)
return return
@ -216,15 +215,15 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
loadTemplate(w, r) loadTemplate(w, r)
render, err := source.TmplBox.String(file) render, err := source.TmplBox.String(file)
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
} }
// render the page requested // render the page requested
if _, err := mainTemplate.Parse(render); err != nil { if _, err := mainTemplate.Parse(render); err != nil {
utils.Log(4, err) log.Errorln(err)
} }
// execute the template // execute the template
if err := mainTemplate.Execute(w, data); err != nil { if err := mainTemplate.Execute(w, data); err != nil {
utils.Log(4, err) log.Errorln(err)
} }
} }
@ -232,7 +231,7 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) { func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
render, err := source.JsBox.String(file) render, err := source.JsBox.String(file)
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
} }
if usingSSL { if usingSSL {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
@ -247,10 +246,10 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
}, },
}) })
if _, err := t.Parse(render); err != nil { if _, err := t.Parse(render); err != nil {
utils.Log(4, err) log.Errorln(err)
} }
if err := t.Execute(w, data); err != nil { if err := t.Execute(w, data); err != nil {
utils.Log(4, err) log.Errorln(err)
} }
} }

View File

@ -1,15 +1,31 @@
package handlers package handlers
import ( import (
"fmt"
"github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/utils"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"time" "time"
) )
// sendLog is a http middleware that will log the duration of request and other useful fields
func sendLog(handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t1 := utils.Now()
handler(w, r)
t2 := utils.Now().Sub(t1)
log.WithFields(utils.ToFields(w, r)).
WithField("url", r.RequestURI).
WithField("method", r.Method).
WithField("load_micro_seconds", t2.Microseconds()).
Infoln(fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host))
})
}
// authenticated is a middleware function to check if user is an Admin before running original request // authenticated is a middleware function to check if user is an Admin before running original request
func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler { func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return sendLog(func(w http.ResponseWriter, r *http.Request) {
if !IsFullAuthenticated(r) { if !IsFullAuthenticated(r) {
if redirect { if redirect {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
@ -24,7 +40,7 @@ func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirec
// readOnly is a middleware function to check if user is a User before running original request // readOnly is a middleware function to check if user is a User before running original request
func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler { func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return sendLog(func(w http.ResponseWriter, r *http.Request) {
if !IsReadAuthenticated(r) { if !IsReadAuthenticated(r) {
if redirect { if redirect {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
@ -39,7 +55,7 @@ func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect boo
// cached is a middleware function that accepts a duration and content type and will cache the response of the original request // cached is a middleware function that accepts a duration and content type and will cache the response of the original request
func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler { func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return sendLog(func(w http.ResponseWriter, r *http.Request) {
content := CacheStorage.Get(r.RequestURI) content := CacheStorage.Get(r.RequestURI)
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")

View File

@ -85,7 +85,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
fakeNotifer, notif, err := notifier.SelectNotifier(method) fakeNotifer, notif, err := notifier.SelectNotifier(method)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("issue saving notifier %v: %v", method, err)) log.Errorln(fmt.Sprintf("issue saving notifier %v: %v", method, err))
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings") ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
return return
} }

View File

@ -25,11 +25,11 @@ import (
"github.com/hunterlong/statping/source" "github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"net/http" "net/http"
"time"
) )
var ( var (
router *mux.Router router *mux.Router
log = utils.Log.WithField("type", "handlers")
) )
// Router returns all of the routes used in Statping. // Router returns all of the routes used in Statping.
@ -58,9 +58,9 @@ func Router() *mux.Router {
r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler)) r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler))
r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET") r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(processSetupHandler)).Methods("POST") r.Handle("/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET") r.Handle("/dashboard", sendLog(dashboardHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST") r.Handle("/dashboard", sendLog(loginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(logoutHandler)) r.Handle("/logout", sendLog(logoutHandler))
r.Handle("/plugins/download/{name}", authenticated(pluginsDownloadHandler, true)) r.Handle("/plugins/download/{name}", authenticated(pluginsDownloadHandler, true))
r.Handle("/plugins/{name}/save", authenticated(pluginSavedHandler, true)).Methods("POST") r.Handle("/plugins/{name}/save", authenticated(pluginSavedHandler, true)).Methods("POST")
r.Handle("/help", authenticated(helpHandler, true)) r.Handle("/help", authenticated(helpHandler, true))
@ -90,11 +90,11 @@ func Router() *mux.Router {
// SERVICE Routes // SERVICE Routes
r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET") r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET")
r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET") r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET")
r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET") r.Handle("/service/{id}", sendLog(servicesViewHandler)).Methods("GET")
r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET") r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET")
r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET") r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET")
r.Handle("/group/{id}", http.HandlerFunc(groupViewHandler)).Methods("GET") r.Handle("/group/{id}", sendLog(groupViewHandler)).Methods("GET")
// API GROUPS Routes // API GROUPS Routes
r.Handle("/api/groups", readOnly(apiAllGroupHandler, false)).Methods("GET") r.Handle("/api/groups", readOnly(apiAllGroupHandler, false)).Methods("GET")
@ -115,9 +115,9 @@ func Router() *mux.Router {
r.Handle("/api/services/{id}", readOnly(apiServiceHandler, false)).Methods("GET") r.Handle("/api/services/{id}", readOnly(apiServiceHandler, false)).Methods("GET")
r.Handle("/api/reorder/services", authenticated(reorderServiceHandler, false)).Methods("POST") r.Handle("/api/reorder/services", authenticated(reorderServiceHandler, false)).Methods("POST")
r.Handle("/api/services/{id}/running", authenticated(apiServiceRunningHandler, false)).Methods("POST") r.Handle("/api/services/{id}/running", authenticated(apiServiceRunningHandler, false)).Methods("POST")
r.Handle("/api/services/{id}/data", cached("30s", "application/json", http.HandlerFunc(apiServiceDataHandler))).Methods("GET") r.Handle("/api/services/{id}/data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
r.Handle("/api/services/{id}/ping", cached("30s", "application/json", http.HandlerFunc(apiServicePingDataHandler))).Methods("GET") r.Handle("/api/services/{id}/ping", cached("30s", "application/json", apiServicePingDataHandler)).Methods("GET")
r.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", http.HandlerFunc(apiServiceHeatmapHandler))).Methods("GET") r.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", apiServiceHeatmapHandler)).Methods("GET")
r.Handle("/api/services/{id}", authenticated(apiServiceUpdateHandler, false)).Methods("POST") r.Handle("/api/services/{id}", authenticated(apiServiceUpdateHandler, false)).Methods("POST")
r.Handle("/api/services/{id}", authenticated(apiServiceDeleteHandler, false)).Methods("DELETE") r.Handle("/api/services/{id}", authenticated(apiServiceDeleteHandler, false)).Methods("DELETE")
r.Handle("/api/services/{id}/failures", authenticated(apiServiceFailuresHandler, false)).Methods("GET") r.Handle("/api/services/{id}/failures", authenticated(apiServiceFailuresHandler, false)).Methods("GET")
@ -152,7 +152,7 @@ func Router() *mux.Router {
r.Handle("/api/checkin/{api}", authenticated(apiCheckinHandler, false)).Methods("GET") r.Handle("/api/checkin/{api}", authenticated(apiCheckinHandler, false)).Methods("GET")
r.Handle("/api/checkin", authenticated(checkinCreateHandler, false)).Methods("POST") r.Handle("/api/checkin", authenticated(checkinCreateHandler, false)).Methods("POST")
r.Handle("/api/checkin/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE") r.Handle("/api/checkin/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler)) r.Handle("/checkin/{api}", sendLog(checkinHitHandler))
// Static Files Routes // Static Files Routes
r.PathPrefix("/files/postman.json").Handler(http.StripPrefix("/files/", http.FileServer(source.TmplBox.HTTPBox()))) r.PathPrefix("/files/postman.json").Handler(http.StripPrefix("/files/", http.FileServer(source.TmplBox.HTTPBox())))
@ -161,10 +161,10 @@ func Router() *mux.Router {
// API Generic Routes // API Generic Routes
r.Handle("/metrics", readOnly(prometheusHandler, false)) r.Handle("/metrics", readOnly(prometheusHandler, false))
r.Handle("/health", http.HandlerFunc(healthCheckHandler)) r.Handle("/health", sendLog(healthCheckHandler))
r.Handle("/.well-known/", http.StripPrefix("/.well-known/", http.FileServer(http.Dir(dir+"/.well-known")))) r.Handle("/.well-known/", http.StripPrefix("/.well-known/", http.FileServer(http.Dir(dir+"/.well-known"))))
r.NotFoundHandler = http.HandlerFunc(error404Handler) r.NotFoundHandler = sendLog(error404Handler)
return r return r
} }
@ -175,7 +175,7 @@ func resetRouter() {
func resetCookies() { func resetCookies() {
if core.CoreApp != nil { if core.CoreApp != nil {
cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, time.Now().Nanosecond()) cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, utils.Now().Nanosecond())
sessionStore = sessions.NewCookieStore([]byte(cookie)) sessionStore = sessions.NewCookieStore([]byte(cookie))
} else { } else {
sessionStore = sessions.NewCookieStore([]byte("secretinfo")) sessionStore = sessions.NewCookieStore([]byte("secretinfo"))

View File

@ -33,8 +33,8 @@ func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/javascript") w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "max-age=60") w.Header().Set("Cache-Control", "max-age=60")
end := time.Now().UTC() end := utils.Now().UTC()
start := time.Now().Add((-24 * 7) * time.Hour).UTC() start := utils.Now().Add((-24 * 7) * time.Hour).UTC()
var srvs []*core.Service var srvs []*core.Service
for _, s := range services { for _, s := range services {
srvs = append(srvs, s.(*core.Service)) srvs = append(srvs, s.(*core.Service))
@ -94,7 +94,7 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
endField := utils.ToInt(fields.Get("end")) endField := utils.ToInt(fields.Get("end"))
group := r.Form.Get("group") group := r.Form.Get("group")
end := time.Now().UTC() end := utils.Now().UTC()
start := end.Add((-24 * 7) * time.Hour).UTC() start := end.Add((-24 * 7) * time.Hour).UTC()
if startField != 0 { if startField != 0 {
@ -243,7 +243,7 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
var monthOutput []*dataXyMonth var monthOutput []*dataXyMonth
start := service.CreatedAt start := service.CreatedAt
//now := time.Now() //now := utils.Now()
sY, sM, _ := start.Date() sY, sM, _ := start.Date()
@ -252,10 +252,10 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
month := int(sM) month := int(sM)
maxMonth := 12 maxMonth := 12
for year := int(sY); year <= time.Now().Year(); year++ { for year := int(sY); year <= utils.Now().Year(); year++ {
if year == time.Now().Year() { if year == utils.Now().Year() {
maxMonth = int(time.Now().Month()) maxMonth = int(utils.Now().Month())
} }
for m := month; m <= maxMonth; m++ { for m := month; m <= maxMonth; m++ {

View File

@ -67,10 +67,9 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on") app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on")
core.CoreApp, err = core.UpdateCore(app) core.CoreApp, err = core.UpdateCore(app)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error())) log.Errorln(fmt.Sprintf("issue updating Core: %v", err.Error()))
} }
//notifiers.OnSettingsSaved(core.CoreApp.ToCore()) //notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings") ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
} }
@ -91,13 +90,13 @@ func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) { func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
dir := utils.Directory dir := utils.Directory
if err := source.CreateAllAssets(dir); err != nil { if err := source.CreateAllAssets(dir); err != nil {
utils.Log(3, err) log.Errorln(err)
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
if err := source.CompileSASS(dir); err != nil { if err := source.CompileSASS(dir); err != nil {
source.CopyToPublic(source.CssBox, dir+"/assets/css", "base.css") source.CopyToPublic(source.CssBox, dir+"/assets/css", "base.css")
utils.Log(3, "Default 'base.css' was inserted because SASS did not work.") log.Errorln("Default 'base.css' was inserted because SASS did not work.")
} }
resetRouter() resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings") ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
@ -105,7 +104,7 @@ func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) { func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
if err := source.DeleteAllAssets(utils.Directory); err != nil { if err := source.DeleteAllAssets(utils.Directory); err != nil {
utils.Log(3, fmt.Errorf("error deleting all assets %v", err)) log.Errorln(fmt.Errorf("error deleting all assets %v", err))
} }
resetRouter() resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings") ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
@ -115,7 +114,7 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
var fileData bytes.Buffer var fileData bytes.Buffer
file, _, err := r.FormFile("file") file, _, err := r.FormFile("file")
if err != nil { if err != nil {
utils.Log(3, fmt.Errorf("error bulk import services: %v", err)) log.Errorln(fmt.Errorf("error bulk import services: %v", err))
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
return return
} }
@ -129,17 +128,17 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
newService, err := commaToService(col) newService, err := commaToService(col)
if err != nil { if err != nil {
utils.Log(3, fmt.Errorf("issue with row %v: %v", i, err)) log.Errorln(fmt.Errorf("issue with row %v: %v", i, err))
continue continue
} }
service := core.ReturnService(newService) service := core.ReturnService(newService)
_, err = service.Create(true) _, err = service.Create(true)
if err != nil { if err != nil {
utils.Log(3, fmt.Errorf("cannot create service %v: %v", col[0], err)) log.Errorln(fmt.Errorf("cannot create service %v: %v", col[0], err))
continue continue
} }
utils.Log(1, fmt.Sprintf("Created new service %v", service.Name)) log.Infoln(fmt.Sprintf("Created new service %v", service.Name))
} }
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings") ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")

View File

@ -57,7 +57,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
domain := r.PostForm.Get("domain") domain := r.PostForm.Get("domain")
email := r.PostForm.Get("email") email := r.PostForm.Get("email")
sample := r.PostForm.Get("sample_data") == "on" sample := r.PostForm.Get("sample_data") == "on"
utils.Log(2, sample) log.Warnln(sample)
dir := utils.Directory dir := utils.Directory
config := &core.DbConfig{ config := &core.DbConfig{
@ -78,21 +78,21 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
} }
if core.Configs, err = config.Save(); err != nil { if core.Configs, err = config.Save(); err != nil {
utils.Log(4, err) log.Errorln(err)
config.Error = err config.Error = err
setupResponseError(w, r, config) setupResponseError(w, r, config)
return return
} }
if core.Configs, err = core.LoadConfigFile(dir); err != nil { if core.Configs, err = core.LoadConfigFile(dir); err != nil {
utils.Log(3, err) log.Errorln(err)
config.Error = err config.Error = err
setupResponseError(w, r, config) setupResponseError(w, r, config)
return return
} }
if err = core.Configs.Connect(false, dir); err != nil { if err = core.Configs.Connect(false, dir); err != nil {
utils.Log(4, err) log.Errorln(err)
core.DeleteConfig() core.DeleteConfig()
config.Error = err config.Error = err
setupResponseError(w, r, config) setupResponseError(w, r, config)
@ -104,7 +104,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
core.CoreApp, err = config.InsertCore() core.CoreApp, err = config.InsertCore()
if err != nil { if err != nil {
utils.Log(4, err) log.Errorln(err)
config.Error = err config.Error = err
setupResponseError(w, r, config) setupResponseError(w, r, config)
return return

View File

@ -1,5 +0,0 @@
build:
docker:
web: Dockerfile
run:
web: statping -port $PORT

167
install.sh Executable file
View File

@ -0,0 +1,167 @@
#!/bin/sh
# Statping installation script for Linux, Mac, and maybe Windows.
#
# This installation script is a modification of Yarn's installation
set -e
reset="\033[0m"
red="\033[31m"
green="\033[32m"
yellow="\033[33m"
cyan="\033[36m"
white="\033[37m"
gpg_key=64B9C6AAE2D55278
gpgurl=https://statping.com/statping.gpg
repo=https://github.com/hunterlong/statping
statping_get_tarball() {
url="https://github.com/hunterlong/statping/releases/latest/download/statping-$1-$2.tar.gz"
printf "$cyan> Downloading latest version for $OS $ARCH...\n$url $reset\n"
# Get both the tarball and its GPG signature
tarball_tmp=`mktemp -t statping.tar.gz.XXXXXXXXXX`
if curl --fail -L -o "$tarball_tmp" "$url"; then
# All this dance is because `tar --strip=1` does not work everywhere
temp=$(mktemp -d statping.XXXXXXXXXX)
tar xzf $tarball_tmp -C "$temp"
statping_verify_integrity "$temp"/statping
printf "$green> Installing to $DEST/statping\n"
mv "$temp"/statping "$DEST"
newversion=`$DEST/statping version`
rm -rf "$temp"
rm $tarball_tmp*
printf "$cyan> Statping is now installed! $reset\n"
printf "$white> Version: $newversion $reset\n"
printf "$white> Repo: $repo $reset\n"
printf "$white> Wiki: $repo/wiki $reset\n"
printf "$white> Issues: $repo/issues $reset\n"
printf "$cyan> Try to run \"statping help\" $reset\n"
else
printf "$red> Failed to download $url.$reset\n"
exit 1;
fi
}
# Verifies the GPG signature of the tarball
statping_verify_integrity() {
# Check if GPG is installed
if [[ -z "$(command -v gpg)" ]]; then
printf "$yellow> WARNING: GPG is not installed, integrity can not be verified!$reset\n"
return
fi
if [ "$statping_GPG" == "no" ]; then
printf "$cyan> WARNING: Skipping GPG integrity check!$reset\n"
return
fi
printf "$cyan> Verifying integrity with gpg key from $gpgurl...$reset\n"
# Grab the public key if it doesn't already exist
gpg --list-keys $gpg_key >/dev/null 2>&1 || (curl -sS $gpgurl | gpg --import)
if [ ! -f "$1.asc" ]; then
printf "$red> Could not download GPG signature for this Statping release. This means the release can not be verified!$reset\n"
statping_verify_or_quit "> Do you really want to continue?"
return
fi
# Actually perform the verification
if gpg --verify "$1.asc" $1; then
printf "$green> GPG signature looks good$reset\n"
else
printf "$red> GPG signature for this Statping release is invalid! This is BAD and may mean the release has been tampered with. It is strongly recommended that you report this to the Statping developers.$reset\n"
statping_verify_or_quit "> Do you really want to continue?"
fi
}
statping_reset() {
unset -f statping_install statping_reset statping_get_tarball statping_verify_integrity statping_verify_or_quit statping_brew_install getOS getArch
}
statping_brew_install() {
if [[ -z "$(command -v brew --version)" ]]; then
printf "${white}Using Brew to install!$reset\n"
printf "${yellow}brew tap hunterlong/statping$reset\n"
brew tap hunterlong/statping
brew install statping
printf "${green}Brew installation is complete!$reset\n"
printf "${yellow}You can use 'brew upgrade' to upgrade Statping next time.$reset\n"
else
statping_get_tarball $OS $ARCH
fi
}
statping_install() {
printf "${white}Installing Statping!$reset\n"
getOS
getArch
if [ -d "$DEST" ]; then
if ! loc="$(type -p "statping version")" || [[ -z $loc ]]; then
specified_version=`curl -sS https://raw.githubusercontent.com/hunterlong/statping/master/version.txt`
statping_version=`statping version`
if [ "$specified_version" = "$statping_version" ]; then
printf "$green> Statping is already at the $specified_version version.$reset\n"
exit 0
fi
fi
fi
if [ "$OS" == "osx" ]; then
statping_brew_install
else
statping_get_tarball $OS $ARCH
fi
statping_reset
}
statping_verify_or_quit() {
read -p "$1 [y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
printf "$red> Aborting$reset\n"
exit 1
fi
}
# get the users operating system
getOS() {
OS="`uname`"
case $OS in
'Linux')
OS='linux'
DEST=/usr/local/bin
alias ls='ls --color=auto'
;;
'FreeBSD')
OS='freebsd'
DEST=/usr/local/bin
alias ls='ls -G'
;;
'WindowsNT')
OS='windows'
DEST=/usr/local/bin
;;
'Darwin')
OS='osx'
DEST=/usr/local/bin
;;
'SunOS')
OS='solaris'
DEST=/usr/local/bin
;;
'AIX') ;;
*) ;;
esac
}
# get 64x or 32 machine arch
getArch() {
MACHINE_TYPE=`uname -m`
if [ ${MACHINE_TYPE} == 'x86_64' ]; then
ARCH="x64"
else
ARCH="x32"
fi
}
cd ~
statping_install $1 $2

View File

@ -89,8 +89,8 @@ func (u *commandLine) OnSave() error {
// OnTest for commandLine triggers when this notifier has been saved // OnTest for commandLine triggers when this notifier has been saved
func (u *commandLine) OnTest() error { func (u *commandLine) OnTest() error {
in, out, err := runCommand(u.Host, u.Var1) in, out, err := runCommand(u.Host, u.Var1)
utils.Log(1, in) utils.Log.Infoln(in)
utils.Log(1, out) utils.Log.Infoln(out)
return err return err
} }

View File

@ -216,7 +216,7 @@ func (u *email) Select() *notifier.Notification {
// OnSave triggers when this notifier has been saved // OnSave triggers when this notifier has been saved
func (u *email) OnSave() error { func (u *email) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here // Do updating stuff here
return nil return nil
} }
@ -235,7 +235,7 @@ func (u *email) OnTest() error {
LastStatusCode: 200, LastStatusCode: 200,
Expected: types.NewNullString("test example"), Expected: types.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>", LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour), CreatedAt: utils.Now().Add(-24 * time.Hour),
} }
email := &emailOutgoing{ email := &emailOutgoing{
To: u.Var2, To: u.Var2,
@ -262,7 +262,7 @@ func (u *email) dialSend(email *emailOutgoing) error {
m.SetHeader("Subject", email.Subject) m.SetHeader("Subject", email.Subject)
m.SetBody("text/html", email.Source) m.SetBody("text/html", email.Source)
if err := mailer.DialAndSend(m); err != nil { if err := mailer.DialAndSend(m); err != nil {
utils.Log(3, fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err)) utils.Log.Errorln(fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
return err return err
} }
return nil return nil
@ -277,11 +277,11 @@ func emailTemplate(contents string, data interface{}) string {
t := template.New("email") t := template.New("email")
t, err := t.Parse(contents) t, err := t.Parse(contents)
if err != nil { if err != nil {
utils.Log(3, err) utils.Log.Errorln(err)
} }
var tpl bytes.Buffer var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil { if err := t.Execute(&tpl, data); err != nil {
utils.Log(2, err) utils.Log.Warnln(err)
} }
result := tpl.String() result := tpl.String()
return result return result

View File

@ -85,7 +85,7 @@ func (u *lineNotifier) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved // OnSave triggers when this notifier has been saved
func (u *lineNotifier) OnSave() error { func (u *lineNotifier) OnSave() error {
msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method) msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method)
utils.Log(1, msg) utils.Log.Infoln(msg)
u.AddQueue("saved", msg) u.AddQueue("saved", msg)
return nil return nil
} }

View File

@ -44,13 +44,13 @@ var TestService = &types.Service{
LastStatusCode: 404, LastStatusCode: 404,
Online: true, Online: true,
LastResponse: "<html>this is an example response</html>", LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour), CreatedAt: utils.Now().Add(-24 * time.Hour),
} }
var TestFailure = &types.Failure{ var TestFailure = &types.Failure{
Issue: "testing", Issue: "testing",
Service: 1, Service: 1,
CreatedAt: time.Now().Add(-12 * time.Hour), CreatedAt: utils.Now().Add(-12 * time.Hour),
} }
var TestUser = &types.User{ var TestUser = &types.User{

View File

@ -99,7 +99,7 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
message := slackMessage{ message := slackMessage{
Service: s, Service: s,
Template: failingTemplate, Template: failingTemplate,
Time: time.Now().Unix(), Time: utils.Now().Unix(),
Issue: f.Issue, Issue: f.Issue,
} }
parseSlackMessage(s.Id, failingTemplate, message) parseSlackMessage(s.Id, failingTemplate, message)
@ -112,7 +112,7 @@ func (u *slack) OnSuccess(s *types.Service) {
message := slackMessage{ message := slackMessage{
Service: s, Service: s,
Template: successTemplate, Template: successTemplate,
Time: time.Now().Unix(), Time: utils.Now().Unix(),
} }
parseSlackMessage(s.Id, successTemplate, message) parseSlackMessage(s.Id, successTemplate, message)
} }

View File

@ -103,7 +103,7 @@ func (u *telegram) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved // OnSave triggers when this notifier has been saved
func (u *telegram) OnSave() error { func (u *telegram) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here // Do updating stuff here

View File

@ -113,7 +113,7 @@ func (u *twilio) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved // OnSave triggers when this notifier has been saved
func (u *twilio) OnSave() error { func (u *twilio) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here // Do updating stuff here

View File

@ -98,7 +98,7 @@ func replaceBodyText(body string, s *types.Service, f *types.Failure) string {
} }
func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
utils.Log(1, fmt.Sprintf("sending body: '%v' to %v as a %v request", body, w.Host, w.Var1)) utils.Log.Infoln(fmt.Sprintf("sending body: '%v' to %v as a %v request", body, w.Host, w.Var1))
client := new(http.Client) client := new(http.Client)
client.Timeout = time.Duration(10 * time.Second) client.Timeout = time.Duration(10 * time.Second)
var buf *bytes.Buffer var buf *bytes.Buffer
@ -136,7 +136,7 @@ func (w *webhooker) OnTest() error {
} }
defer resp.Body.Close() defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body) content, err := ioutil.ReadAll(resp.Body)
utils.Log(1, fmt.Sprintf("Webhook notifier received: '%v'", string(content))) utils.Log.Infoln(fmt.Sprintf("Webhook notifier received: '%v'", string(content)))
return err return err
} }

View File

@ -41,6 +41,7 @@ import (
var ( var (
AllPlugins []*types.PluginObject AllPlugins []*types.PluginObject
dir string dir string
log = utils.Log.WithField("type", "plugin")
) )
func init() { func init() {
@ -49,7 +50,7 @@ func init() {
} }
func LoadPlugin(file string) error { func LoadPlugin(file string) error {
utils.Log(1, fmt.Sprintf("opening file %v", file)) log.Infoln(fmt.Sprintf("opening file %v", file))
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
return err return err
@ -58,29 +59,29 @@ func LoadPlugin(file string) error {
fSplit := strings.Split(f.Name(), "/") fSplit := strings.Split(f.Name(), "/")
fileBin := fSplit[len(fSplit)-1] fileBin := fSplit[len(fSplit)-1]
utils.Log(1, fmt.Sprintf("Attempting to load plugin '%v'", fileBin)) log.Infoln(fmt.Sprintf("Attempting to load plugin '%v'", fileBin))
ext := strings.Split(fileBin, ".") ext := strings.Split(fileBin, ".")
if len(ext) != 2 { if len(ext) != 2 {
utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin)) log.Errorln(fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin))
return fmt.Errorf("Plugin '%v' must end in .so extension %v", fileBin, len(ext)) return fmt.Errorf("Plugin '%v' must end in .so extension %v", fileBin, len(ext))
} }
if ext[1] != "so" { if ext[1] != "so" {
utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin)) log.Errorln(fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin))
return fmt.Errorf("Plugin '%v' must end in .so extension", fileBin) return fmt.Errorf("Plugin '%v' must end in .so extension", fileBin)
} }
plug, err := plugin.Open(file) plug, err := plugin.Open(file)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", fileBin, err)) log.Errorln(fmt.Sprintf("Plugin '%v' could not load correctly. %v", fileBin, err))
return err return err
} }
symPlugin, err := plug.Lookup("Plugin") symPlugin, err := plug.Lookup("Plugin")
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Plugin '%v' could not locate Plugin variable. %v", fileBin, err)) log.Errorln(fmt.Sprintf("Plugin '%v' could not locate Plugin variable. %v", fileBin, err))
return err return err
} }
plugActions, ok := symPlugin.(types.PluginActions) plugActions, ok := symPlugin.(types.PluginActions)
if !ok { if !ok {
utils.Log(3, fmt.Sprintf("Plugin %v was not type PluginObject", f.Name())) log.Errorln(fmt.Sprintf("Plugin %v was not type PluginObject", f.Name()))
return fmt.Errorf("Plugin %v was not type PluginActions %v", f.Name(), plugActions.GetInfo()) return fmt.Errorf("Plugin %v was not type PluginActions %v", f.Name(), plugActions.GetInfo())
} }
info := plugActions.GetInfo() info := plugActions.GetInfo()
@ -88,28 +89,28 @@ func LoadPlugin(file string) error {
if err != nil { if err != nil {
return err return err
} }
utils.Log(1, fmt.Sprintf("Plugin %v loaded from %v", info.Name, f.Name())) log.Infoln(fmt.Sprintf("Plugin %v loaded from %v", info.Name, f.Name()))
core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions) core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions)
return nil return nil
} }
func LoadPlugins() { func LoadPlugins() {
pluginDir := dir + "/plugins" pluginDir := dir + "/plugins"
utils.Log(1, fmt.Sprintf("Loading any available Plugins from /plugins directory")) log.Infoln(fmt.Sprintf("Loading any available Plugins from /plugins directory"))
if _, err := os.Stat(pluginDir); os.IsNotExist(err) { if _, err := os.Stat(pluginDir); os.IsNotExist(err) {
os.Mkdir(pluginDir, os.ModePerm) os.Mkdir(pluginDir, os.ModePerm)
} }
files, err := ioutil.ReadDir(pluginDir) files, err := ioutil.ReadDir(pluginDir)
if err != nil { if err != nil {
utils.Log(2, fmt.Sprintf("Plugins directory was not found. Error: %v", err)) log.Warnln(fmt.Sprintf("Plugins directory was not found. Error: %v", err))
return return
} }
for _, f := range files { for _, f := range files {
err := LoadPlugin(f.Name()) err := LoadPlugin(f.Name())
if err != nil { if err != nil {
utils.Log(3, err) log.Errorln(err)
continue continue
} }
} }
utils.Log(1, fmt.Sprintf("Loaded %v Plugins", len(core.CoreApp.Plugins))) log.Infoln(fmt.Sprintf("Loaded %v Plugins", len(core.CoreApp.Plugins)))
} }

View File

@ -7,54 +7,66 @@
/* Mobile Service Container */ /* Mobile Service Container */
HTML, BODY { HTML, BODY {
background-color: #fcfcfc; background-color: #fcfcfc;
padding-bottom: 10px; } padding-bottom: 10px;
}
.container { .container {
padding-top: 20px; padding-top: 20px;
padding-bottom: 25px; padding-bottom: 25px;
max-width: 860px; max-width: 860px;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.header-title { .header-title {
color: #464646; } color: #464646;
}
.header-desc { .header-desc {
color: #939393; } color: #939393;
}
.btn { .btn {
border-radius: 0.2rem; } border-radius: 0.2rem;
}
.online_list .badge { .online_list .badge {
margin-top: 0.2rem; } margin-top: 0.2rem;
}
.navbar { .navbar {
margin-bottom: 30px; } margin-bottom: 30px;
}
.btn-sm { .btn-sm {
line-height: 1.3; line-height: 1.3;
font-size: 0.75rem; } font-size: 0.75rem;
}
.view_service_btn { .view_service_btn {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
right: 40px; } right: 40px;
}
.service_lower_info { .service_lower_info {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
left: 40px; left: 40px;
color: #d1ffca; color: #d1ffca;
font-size: 0.85rem; } font-size: 0.85rem;
}
.lg_number { .lg_number {
font-size: 2.3rem; font-size: 2.3rem;
font-weight: bold; font-weight: bold;
display: block; display: block;
color: #4f4f4f; } color: #4f4f4f;
}
.stats_area { .stats_area {
text-align: center; text-align: center;
color: #a5a5a5; } color: #a5a5a5;
}
.lower_canvas { .lower_canvas {
height: 3.4rem; height: 3.4rem;
@ -62,82 +74,101 @@ HTML, BODY {
background-color: #48d338; background-color: #48d338;
padding: 15px 10px; padding: 15px 10px;
margin-left: 0px !important; margin-left: 0px !important;
margin-right: 0px !important; } margin-right: 0px !important;
}
.lower_canvas SPAN { .lower_canvas SPAN {
font-size: 1rem; font-size: 1rem;
color: #fff; } color: #fff;
}
.footer { .footer {
text-decoration: none; text-decoration: none;
margin-top: 20px; } margin-top: 20px;
}
.footer A { .footer A {
color: #8d8d8d; color: #8d8d8d;
text-decoration: none; } text-decoration: none;
}
.footer A:HOVER { .footer A:HOVER {
color: #6d6d6d; } color: #6d6d6d;
}
.badge { .badge {
color: white; color: white;
border-radius: 0.2rem; } border-radius: 0.2rem;
}
.btn-group { .btn-group {
height: 25px; } height: 25px;
.btn-group A { }
padding: 0.1rem .75rem; .btn-group A {
font-size: 0.8rem; } padding: 0.1rem 0.75rem;
font-size: 0.8rem;
}
.card-body .badge { .card-body .badge {
color: #fff; } color: #fff;
}
.nav-pills .nav-link { .nav-pills .nav-link {
border-radius: 0.2rem; } border-radius: 0.2rem;
}
.form-control { .form-control {
border-radius: 0.2rem; } border-radius: 0.2rem;
}
.card { .card {
background-color: #ffffff; background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.125); } border: 1px solid rgba(0, 0, 0, 0.125);
}
.card-body { .card-body {
overflow: hidden; } overflow: hidden;
}
.card-body H4 A { .card-body H4 A {
color: #444444; color: #444444;
text-decoration: none; } text-decoration: none;
}
.chart-container { .chart-container {
position: relative; position: relative;
height: 170px; height: 170px;
width: 100%; width: 100%;
overflow: hidden; } overflow: hidden;
}
.service-chart-container { .service-chart-container {
position: relative; position: relative;
height: 400px; height: 400px;
width: 100%; } width: 100%;
}
.service-chart-heatmap { .service-chart-heatmap {
position: relative; position: relative;
height: 300px; height: 300px;
width: 100%; } width: 100%;
}
.inputTags-field { .inputTags-field {
border: 0; border: 0;
background-color: transparent; background-color: transparent;
padding-top: .13rem; } padding-top: 0.13rem;
}
input.inputTags-field:focus { input.inputTags-field:focus {
outline-width: 0; } outline-width: 0;
}
.inputTags-list { .inputTags-list {
display: block; display: block;
width: 100%; width: 100%;
min-height: calc(2.25rem + 2px); min-height: calc(2.25rem + 2px);
padding: .2rem .35rem; padding: 0.2rem 0.35rem;
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -145,8 +176,9 @@ input.inputTags-field:focus {
background-color: #fff; background-color: #fff;
background-clip: padding-box; background-clip: padding-box;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: .25rem; border-radius: 0.25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; } transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.inputTags-item { .inputTags-item {
background-color: #3aba39; background-color: #3aba39;
@ -154,63 +186,81 @@ input.inputTags-field:focus {
padding: 5px 8px; padding: 5px 8px;
font-size: 10pt; font-size: 10pt;
color: white; color: white;
border-radius: 4px; } border-radius: 4px;
}
.inputTags-item .close-item { .inputTags-item .close-item {
margin-left: 6px; margin-left: 6px;
font-size: 13pt; font-size: 13pt;
font-weight: bold; font-weight: bold;
cursor: pointer; } cursor: pointer;
}
.btn-primary { .btn-primary {
background-color: #3e9bff; background-color: #3e9bff;
border-color: #006fe6; border-color: #006fe6;
color: white; } color: white;
.btn-primary.dyn-dark { }
background-color: #32a825 !important; .btn-primary.dyn-dark {
border-color: #2c9320 !important; } background-color: #32a825 !important;
.btn-primary.dyn-light { border-color: #2c9320 !important;
background-color: #75de69 !important; }
border-color: #88e37e !important; } .btn-primary.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important;
}
.btn-success { .btn-success {
background-color: #47d337; } background-color: #47d337;
.btn-success.dyn-dark { }
background-color: #32a825 !important; .btn-success.dyn-dark {
border-color: #2c9320 !important; } background-color: #32a825 !important;
.btn-success.dyn-light { border-color: #2c9320 !important;
background-color: #75de69 !important; }
border-color: #88e37e !important; } .btn-success.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important;
}
.btn-danger { .btn-danger {
background-color: #dd3545; } background-color: #dd3545;
.btn-danger.dyn-dark { }
background-color: #b61f2d !important; .btn-danger.dyn-dark {
border-color: #a01b28 !important; } background-color: #b61f2d !important;
.btn-danger.dyn-light { border-color: #a01b28 !important;
background-color: #e66975 !important; }
border-color: #e97f89 !important; } .btn-danger.dyn-light {
background-color: #e66975 !important;
border-color: #e97f89 !important;
}
.bg-success { .bg-success {
background-color: #47d337 !important; } background-color: #47d337 !important;
}
.bg-danger { .bg-danger {
background-color: #dd3545 !important; } background-color: #dd3545 !important;
}
.bg-success .dyn-dark { .bg-success .dyn-dark {
background-color: #35b027 !important; } background-color: #35b027 !important;
}
.bg-danger .dyn-dark { .bg-danger .dyn-dark {
background-color: #bf202f !important; } background-color: #bf202f !important;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link { .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
background-color: #13a00d; } background-color: #13a00d;
}
.nav-pills A { .nav-pills A {
color: #424242; } color: #424242;
}
.nav-pills I { .nav-pills I {
margin-right: 10px; } margin-right: 10px;
}
.CodeMirror { .CodeMirror {
/* Bootstrap Settings */ /* Bootstrap Settings */
@ -230,23 +280,26 @@ input.inputTags-field:focus {
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
/* Code Mirror Settings */ /* Code Mirror Settings */
font-family: monospace; font-family: monospace;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
height: 80vh; } height: 80vh;
}
.CodeMirror-focused { .CodeMirror-focused {
/* Bootstrap Settings */ /* Bootstrap Settings */
border-color: #66afe9; border-color: #66afe9;
outline: 0; outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; } transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
.switch { .switch {
font-size: 1rem; font-size: 1rem;
position: relative; } position: relative;
}
.switch input { .switch input {
position: absolute; position: absolute;
@ -257,7 +310,8 @@ input.inputTags-field:focus {
clip: rect(0 0 0 0); clip: rect(0 0 0 0);
clip-path: inset(50%); clip-path: inset(50%);
overflow: hidden; overflow: hidden;
padding: 0; } padding: 0;
}
.switch input + label { .switch input + label {
position: relative; position: relative;
@ -270,23 +324,26 @@ input.inputTags-field:focus {
outline: none; outline: none;
user-select: none; user-select: none;
vertical-align: middle; vertical-align: middle;
text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); } text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem);
}
.switch input + label::before, .switch input + label::before,
.switch input + label::after { .switch input + label::after {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: calc(calc(2.375rem * .8) * 2); width: calc(calc(2.375rem * .8) * 2);
bottom: 0; bottom: 0;
display: block; } display: block;
}
.switch input + label::before { .switch input + label::before {
right: 0; right: 0;
background-color: #dee2e6; background-color: #dee2e6;
border-radius: calc(2.375rem * .8); border-radius: calc(2.375rem * .8);
transition: 0.2s all; } transition: 0.2s all;
}
.switch input + label::after { .switch input + label::after {
top: 2px; top: 2px;
@ -295,105 +352,137 @@ input.inputTags-field:focus {
height: calc(calc(2.375rem * .8) - calc(2px * 2)); height: calc(calc(2.375rem * .8) - calc(2px * 2));
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
transition: 0.2s all; } transition: 0.2s all;
}
.switch input:checked + label::before { .switch input:checked + label::before {
background-color: #08d; } background-color: #08d;
}
.switch input:checked + label::after { .switch input:checked + label::after {
margin-left: calc(2.375rem * .8); } margin-left: calc(2.375rem * .8);
}
.switch input:focus + label::before { .switch input:focus + label::before {
outline: none; outline: none;
box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); } box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25);
}
.switch input:disabled + label { .switch input:disabled + label {
color: #868e96; color: #868e96;
cursor: not-allowed; } cursor: not-allowed;
}
.switch input:disabled + label::before { .switch input:disabled + label::before {
background-color: #e9ecef; } background-color: #e9ecef;
}
.switch.switch-sm { .switch.switch-sm {
font-size: 0.875rem; } font-size: 0.875rem;
}
.switch.switch-sm input + label { .switch.switch-sm input + label {
min-width: calc(calc(1.9375rem * .8) * 2); min-width: calc(calc(1.9375rem * .8) * 2);
height: calc(1.9375rem * .8); height: calc(1.9375rem * .8);
line-height: calc(1.9375rem * .8); line-height: calc(1.9375rem * .8);
text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); } text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem);
}
.switch.switch-sm input + label::before { .switch.switch-sm input + label::before {
width: calc(calc(1.9375rem * .8) * 2); } width: calc(calc(1.9375rem * .8) * 2);
}
.switch.switch-sm input + label::after { .switch.switch-sm input + label::after {
width: calc(calc(1.9375rem * .8) - calc(2px * 2)); width: calc(calc(1.9375rem * .8) - calc(2px * 2));
height: calc(calc(1.9375rem * .8) - calc(2px * 2)); } height: calc(calc(1.9375rem * .8) - calc(2px * 2));
}
.switch.switch-sm input:checked + label::after { .switch.switch-sm input:checked + label::after {
margin-left: calc(1.9375rem * .8); } margin-left: calc(1.9375rem * .8);
}
.switch.switch-lg { .switch.switch-lg {
font-size: 1.25rem; } font-size: 1.25rem;
}
.switch.switch-lg input + label { .switch.switch-lg input + label {
min-width: calc(calc(3rem * .8) * 2); min-width: calc(calc(3rem * .8) * 2);
height: calc(3rem * .8); height: calc(3rem * .8);
line-height: calc(3rem * .8); line-height: calc(3rem * .8);
text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); } text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem);
}
.switch.switch-lg input + label::before { .switch.switch-lg input + label::before {
width: calc(calc(3rem * .8) * 2); } width: calc(calc(3rem * .8) * 2);
}
.switch.switch-lg input + label::after { .switch.switch-lg input + label::after {
width: calc(calc(3rem * .8) - calc(2px * 2)); width: calc(calc(3rem * .8) - calc(2px * 2));
height: calc(calc(3rem * .8) - calc(2px * 2)); } height: calc(calc(3rem * .8) - calc(2px * 2));
}
.switch.switch-lg input:checked + label::after { .switch.switch-lg input:checked + label::after {
margin-left: calc(3rem * .8); } margin-left: calc(3rem * .8);
}
.switch + .switch { .switch + .switch {
margin-left: 1rem; } margin-left: 1rem;
}
@keyframes pulse_animation { @keyframes pulse_animation {
0% { 0% {
transform: scale(1); } transform: scale(1);
}
30% { 30% {
transform: scale(1); } transform: scale(1);
}
40% { 40% {
transform: scale(1.02); } transform: scale(1.02);
}
50% { 50% {
transform: scale(1); } transform: scale(1);
}
60% { 60% {
transform: scale(1); } transform: scale(1);
}
70% { 70% {
transform: scale(1.05); } transform: scale(1.05);
}
80% { 80% {
transform: scale(1); } transform: scale(1);
}
100% { 100% {
transform: scale(1); } } transform: scale(1);
}
}
.pulse { .pulse {
animation-name: pulse_animation; animation-name: pulse_animation;
animation-duration: 1500ms; animation-duration: 1500ms;
transform-origin: 70% 70%; transform-origin: 70% 70%;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: linear; } animation-timing-function: linear;
}
@keyframes glow-grow { @keyframes glow-grow {
0% { 0% {
opacity: 0; opacity: 0;
transform: scale(1); } transform: scale(1);
}
80% { 80% {
opacity: 1; } opacity: 1;
}
100% { 100% {
transform: scale(2); transform: scale(2);
opacity: 0; } } opacity: 0;
}
}
.pulse-glow { .pulse-glow {
animation-name: glow-grown; animation-name: glow-grown;
animation-duration: 100ms; animation-duration: 100ms;
transform-origin: 70% 30%; transform-origin: 70% 30%;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: linear; } animation-timing-function: linear;
}
.pulse-glow:before, .pulse-glow:before,
.pulse-glow:after { .pulse-glow:after {
@ -405,10 +494,12 @@ input.inputTags-field:focus {
right: 2.15rem; right: 2.15rem;
border-radius: 0; border-radius: 0;
box-shadow: 0 0 6px #47d337; box-shadow: 0 0 6px #47d337;
animation: glow-grow 2s ease-out infinite; } animation: glow-grow 2s ease-out infinite;
}
.sortable_drag { .sortable_drag {
background-color: #0000000f; } background-color: #0000000f;
}
.drag_icon { .drag_icon {
cursor: move; cursor: move;
@ -422,112 +513,139 @@ input.inputTags-field:focus {
margin-right: 5px; margin-right: 5px;
margin-left: -10px; margin-left: -10px;
text-align: center; text-align: center;
color: #b1b1b1; } color: #b1b1b1;
}
/* (Optional) Apply a "closed-hand" cursor during drag operation. */ /* (Optional) Apply a "closed-hand" cursor during drag operation. */
.drag_icon:active { .drag_icon:active {
cursor: grabbing; cursor: grabbing;
cursor: -moz-grabbing; cursor: -moz-grabbing;
cursor: -webkit-grabbing; } cursor: -webkit-grabbing;
}
.switch_btn { .switch_btn {
float: right; float: right;
margin: -1px 0px 0px 0px; margin: -1px 0px 0px 0px;
display: block; } display: block;
}
#start_container { #start_container {
position: absolute; position: absolute;
z-index: 99999; z-index: 99999;
margin-top: 20px; } margin-top: 20px;
}
#end_container { #end_container {
position: absolute; position: absolute;
z-index: 99999; z-index: 99999;
margin-top: 20px; margin-top: 20px;
right: 0; } right: 0;
}
.pointer { .pointer {
cursor: pointer; } cursor: pointer;
}
.jumbotron { .jumbotron {
background-color: white; } background-color: white;
}
.toggle-service { .toggle-service {
font-size: 18pt; font-size: 18pt;
float: left; float: left;
margin: 2px 3px 0 0; margin: 2px 3px 0 0;
cursor: pointer; } cursor: pointer;
}
@media (max-width: 767px) { @media (max-width: 767px) {
HTML, BODY { HTML, BODY {
background-color: #fcfcfc; } background-color: #fcfcfc;
}
.sm-container { .sm-container {
margin-top: 0px !important; margin-top: 0px !important;
padding: 0 !important; } padding: 0 !important;
}
.list-group-item H5 { .list-group-item H5 {
font-size: 0.9rem; } font-size: 0.9rem;
}
.container { .container {
padding: 0px !important; padding: 0px !important;
padding-top: 15px !important; } padding-top: 15px !important;
}
.group_header { .group_header {
margin-left: 15px; } margin-left: 15px;
}
.navbar { .navbar {
margin-left: 0px; margin-left: 0px;
margin-top: 0px; margin-top: 0px;
width: 100%; width: 100%;
margin-bottom: 0; } margin-bottom: 0;
}
.btn-sm { .btn-sm {
line-height: 0.9rem; line-height: 0.9rem;
font-size: 0.65rem; } font-size: 0.65rem;
}
.full-col-12 { .full-col-12 {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; } padding-right: 0px;
}
.card { .card {
border: 0; border: 0;
border-radius: 0rem; border-radius: 0rem;
padding: 0; padding: 0;
background-color: #ffffff; } background-color: #ffffff;
}
.card-body { .card-body {
font-size: 10pt; font-size: 10pt;
padding: 0px 10px; } padding: 10px 10px;
}
.lg_number { .lg_number {
font-size: 7.8vw; } font-size: 7.8vw;
}
.stats_area { .stats_area {
margin-top: 1.5rem !important; margin-top: 1.5rem !important;
margin-bottom: 1.5rem !important; } margin-bottom: 1.5rem !important;
}
.stats_area .col-4 { .stats_area .col-4 {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
font-size: 0.6rem; } font-size: 0.6rem;
}
.list-group-item { .list-group-item {
border-top: 1px solid #e4e4e4; border-top: 1px solid #e4e4e4;
border: 0px; } border: 0px;
}
.list-group-item:first-child { .list-group-item:first-child {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; } border-top-right-radius: 0;
}
.list-group-item:last-child { .list-group-item:last-child {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-bottom-left-radius: 0; } border-bottom-left-radius: 0;
}
.list-group-item P { .list-group-item P {
font-size: 0.7rem; } font-size: 0.7rem;
}
.service-chart-container { .service-chart-container {
height: 200px; } } height: 200px;
}
}
/*# sourceMappingURL=base.css.map */ /*# sourceMappingURL=base.css.map */

View File

@ -67,7 +67,7 @@ func main() {
Compiled string Compiled string
Pages map[string]string Pages map[string]string
}{ }{
Timestamp: time.Now(), Timestamp: utils.Now(),
URL: "ok", URL: "ok",
Compiled: compiled, Compiled: compiled,
Pages: newPages, Pages: newPages,

View File

@ -28,6 +28,7 @@ import (
) )
var ( var (
log = utils.Log.WithField("type", "source")
CssBox *rice.Box // CSS files from the 'source/css' directory, this will be loaded into '/assets/css' 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' 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' JsBox *rice.Box // JS files from the 'source/js' directory, this will be loaded into '/assets/js'
@ -60,24 +61,24 @@ func CompileSASS(folder string) error {
scssFile := fmt.Sprintf("%v/%v", folder, "assets/scss/base.scss") scssFile := fmt.Sprintf("%v/%v", folder, "assets/scss/base.scss")
baseFile := fmt.Sprintf("%v/%v", folder, "assets/css/base.css") baseFile := fmt.Sprintf("%v/%v", folder, "assets/css/base.css")
utils.Log(1, fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile)) log.Infoln(fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile))
command := fmt.Sprintf("%v %v %v", sassBin, scssFile, baseFile) command := fmt.Sprintf("%v %v %v", sassBin, scssFile, baseFile)
stdout, stderr, err := utils.Command(command) stdout, stderr, err := utils.Command(command)
if stdout != "" || stderr != "" { if stdout != "" || stderr != "" {
utils.Log(3, fmt.Sprintf("Failed to compile assets with SASS %v", err)) log.Errorln(fmt.Sprintf("Failed to compile assets with SASS %v", err))
return errors.New("failed to capture stdout or stderr") return errors.New("failed to capture stdout or stderr")
} }
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Failed to compile assets with SASS %v", err)) log.Errorln(fmt.Sprintf("Failed to compile assets with SASS %v", err))
utils.Log(3, fmt.Sprintf("sh -c %v", command)) log.Errorln(fmt.Sprintf("sh -c %v", command))
return err return err
} }
utils.Log(1, fmt.Sprintf("out: %v | error: %v", stdout, stderr)) log.Infoln(fmt.Sprintf("out: %v | error: %v", stdout, stderr))
utils.Log(1, "SASS Compiling is complete!") log.Infoln("SASS Compiling is complete!")
return nil return nil
} }
@ -87,12 +88,12 @@ func UsingAssets(folder string) bool {
return true return true
} else { } else {
if os.Getenv("USE_ASSETS") == "true" { if os.Getenv("USE_ASSETS") == "true" {
utils.Log(1, "Environment variable USE_ASSETS was found.") log.Infoln("Environment variable USE_ASSETS was found.")
CreateAllAssets(folder) CreateAllAssets(folder)
err := CompileSASS(folder) err := CompileSASS(folder)
if err != nil { if err != nil {
CopyToPublic(CssBox, folder+"/css", "base.css") CopyToPublic(CssBox, folder+"/css", "base.css")
utils.Log(2, "Default 'base.css' was insert because SASS did not work.") log.Warnln("Default 'base.css' was insert because SASS did not work.")
return true return true
} }
return true return true
@ -104,10 +105,10 @@ func UsingAssets(folder string) bool {
// SaveAsset will save an asset to the '/assets/' folder. // 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 {
location := folder + "/assets/" + file location := folder + "/assets/" + file
utils.Log(1, fmt.Sprintf("Saving %v", location)) log.Infoln(fmt.Sprintf("Saving %v", location))
err := utils.SaveFile(location, data) err := utils.SaveFile(location, data)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Failed to save %v, %v", location, err)) log.Errorln(fmt.Sprintf("Failed to save %v, %v", location, err))
return err return err
} }
return nil return nil
@ -117,7 +118,7 @@ func SaveAsset(data []byte, folder, file string) error {
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 {
utils.Log(3, fmt.Sprintf("Failed to open %v, %v", file, err)) log.Errorln(fmt.Sprintf("Failed to open %v, %v", file, err))
return "" return ""
} }
return string(dat) return string(dat)
@ -125,14 +126,14 @@ func OpenAsset(folder, file string) string {
// CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory // 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 Statping assets into %v/assets", folder)) log.Infoln(fmt.Sprintf("Dump Statping assets into %v/assets", folder))
MakePublicFolder(folder + "/assets") MakePublicFolder(folder + "/assets")
MakePublicFolder(folder + "/assets/js") MakePublicFolder(folder + "/assets/js")
MakePublicFolder(folder + "/assets/css") MakePublicFolder(folder + "/assets/css")
MakePublicFolder(folder + "/assets/scss") MakePublicFolder(folder + "/assets/scss")
MakePublicFolder(folder + "/assets/font") MakePublicFolder(folder + "/assets/font")
MakePublicFolder(folder + "/assets/files") MakePublicFolder(folder + "/assets/files")
utils.Log(1, "Inserting scss, css, and javascript files into assets folder") log.Infoln("Inserting scss, css, and javascript files into assets folder")
CopyAllToPublic(ScssBox, "scss") CopyAllToPublic(ScssBox, "scss")
CopyAllToPublic(FontBox, "font") CopyAllToPublic(FontBox, "font")
CopyAllToPublic(CssBox, "css") CopyAllToPublic(CssBox, "css")
@ -144,9 +145,9 @@ func CreateAllAssets(folder string) error {
CopyToPublic(TmplBox, folder+"/assets/files", "swagger.json") CopyToPublic(TmplBox, folder+"/assets/files", "swagger.json")
CopyToPublic(TmplBox, folder+"/assets/files", "postman.json") CopyToPublic(TmplBox, folder+"/assets/files", "postman.json")
CopyToPublic(TmplBox, folder+"/assets/files", "grafana.json") CopyToPublic(TmplBox, folder+"/assets/files", "grafana.json")
utils.Log(1, "Compiling CSS from SCSS style...") log.Infoln("Compiling CSS from SCSS style...")
err := CompileSASS(utils.Directory) err := CompileSASS(utils.Directory)
utils.Log(1, "Statping assets have been inserted") log.Infoln("Statping assets have been inserted")
return err return err
} }
@ -154,10 +155,10 @@ func CreateAllAssets(folder string) error {
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 {
utils.Log(1, fmt.Sprintf("There was an issue deleting Statping Assets, %v", err)) log.Infoln(fmt.Sprintf("There was an issue deleting Statping Assets, %v", err))
return err return err
} }
utils.Log(1, "Statping assets have been deleted") log.Infoln("Statping assets have been deleted")
return err return err
} }
@ -184,15 +185,15 @@ func CopyAllToPublic(box *rice.Box, folder string) error {
// CopyToPublic will create a file from a rice Box to the '/assets' directory // 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)) log.Infoln(fmt.Sprintf("Copying %v to %v", file, assetFolder))
base, err := box.String(file) base, err := box.String(file)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err)) log.Errorln(fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err))
return err return err
} }
err = ioutil.WriteFile(assetFolder, []byte(base), 0744) err = ioutil.WriteFile(assetFolder, []byte(base), 0744)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err)) log.Errorln(fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err))
return err return err
} }
return nil return nil
@ -200,11 +201,11 @@ func CopyToPublic(box *rice.Box, folder, file string) error {
// MakePublicFolder will create a new folder // 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)) log.Infoln(fmt.Sprintf("Creating folder '%v'", folder))
if _, err := os.Stat(folder); os.IsNotExist(err) { if _, err := os.Stat(folder); os.IsNotExist(err) {
err = os.MkdirAll(folder, 0777) err = os.MkdirAll(folder, 0777)
if err != nil { if err != nil {
utils.Log(3, fmt.Sprintf("Failed to created %v directory, %v", folder, err)) log.Errorln(fmt.Sprintf("Failed to created %v directory, %v", folder, err))
return err return err
} }
} }

View File

@ -46,7 +46,7 @@
<div class="jumbotron jumbotron-fluid"> <div class="jumbotron jumbotron-fluid">
<div class="text-center"> <div class="text-center">
<h1 class="display-4">No Services!</h1> <h1 class="display-4">No Services!</h1>
<a class="lead">You don't have any websites or applications being monitored by your Statping server. <p><a href="/services" class="btn btn-secondary mt-3">Add Service</a></p></p> <a class="lead">You don't have any websites or applications being monitored by your Statping server. <p><a href="/service/create" class="btn btn-secondary mt-3">Add Service</a></p></p>
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@ -47,7 +47,7 @@
{{ if not .Services }} {{ if not .Services }}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
<h4 class="alert-heading">No Services to Monitor!</h4> <h4 class="alert-heading">No Services to Monitor!</h4>
<p>Your Statping Status Page is working correctly, but you don't have any services to monitor. Go to the <b>Dashboard</b> and add a website to begin really using your status page!</p> <p>Your Statping Status Page is working correctly, but you don't have any services to monitor. Go to the <a href="/dashboard">Dashboard</a> and add a website to begin really using your status page!</p>
<hr> <hr>
<p class="mb-0">If this is a bug, please make an issue in the Statping Github Repo. <a href="https://github.com/hunterlong/statping" class="btn btn-sm btn-outline-danger float-right">Statping Github Repo</a></p> <p class="mb-0">If this is a bug, please make an issue in the Statping Github Repo. <a href="https://github.com/hunterlong/statping" class="btn btn-sm btn-outline-danger float-right">Statping Github Repo</a></p>
</div> </div>

View File

@ -41,6 +41,7 @@
</div> </div>
<div class="col-12 mt-5"> <div class="col-12 mt-5">
{{if ne (len (Groups false)) 0}}
<h1 class="text-muted">Groups</h1> <h1 class="text-muted">Groups</h1>
<table class="table"> <table class="table">
<thead> <thead>
@ -67,6 +68,7 @@
{{end}} {{end}}
</tbody> </tbody>
</table> </table>
{{end}}
{{if Auth}} {{if Auth}}
<h1 class="text-muted mt-5">Create Group</h1> <h1 class="text-muted mt-5">Create Group</h1>
{{template "form_group" NewGroup}} {{template "form_group" NewGroup}}

File diff suppressed because one or more lines are too long

View File

@ -17,24 +17,137 @@ package utils
import ( import (
"fmt" "fmt"
"github.com/fatih/structs"
"github.com/hunterlong/statping/types"
Logger "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
"log" "io"
"net/http"
"os" "os"
"os/signal" "reflect"
"runtime"
"strings"
"sync" "sync"
"syscall"
"time" "time"
) )
var ( var (
logFile *os.File Log = Logger.StandardLogger()
fmtLogs *log.Logger ljLogger *lumberjack.Logger
ljLogger *lumberjack.Logger LastLines []*LogRow
LastLines []*LogRow LockLines sync.Mutex
LockLines sync.Mutex VerboseMode int
callerInitOnce sync.Once
logrusPackage string
minimumCallerDepth = 1
) )
const logFilePath = "/logs/statping.log"
type hook struct {
Entries []Logger.Entry
mu sync.RWMutex
}
func (t *hook) Fire(e *Logger.Entry) error {
pushLastLine(e.Message)
return nil
}
func (t *hook) Levels() []Logger.Level {
return Logger.AllLevels
}
// LogCaller retrieves the name of the first non-logrus calling function
func LogCaller(min int) *runtime.Frame {
const maximumCallerDepth = 25
var minimumCallerDepth = min
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name())
fmt.Println("caller once", logrusPackage, minimumCallerDepth, frames)
})
for f, again := frames.Next(); again; f, again = frames.Next() {
fmt.Println(f.Func, f.File, f.Line)
return &f
}
// if we got here, we failed to find the caller's context
return nil
}
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}
return f
}
// ToFields accepts any amount of interfaces to create a new mapping for log.Fields. You will need to
// turn on verbose mode by starting Statping with "-v". This function will convert a struct of to the
// base struct name, and each field into it's own mapping, for example:
// type "*types.Service", on string field "Name" converts to "service_name=value". There is also an
// additional field called "_pointer" that will return the pointer hex value.
func ToFields(d ...interface{}) map[string]interface{} {
if VerboseMode == 1 {
return nil
}
fieldKey := make(map[string]interface{})
for _, v := range d {
spl := strings.Split(fmt.Sprintf("%T", v), ".")
trueType := spl[len(spl)-1]
if !structs.IsStruct(v) {
continue
}
for _, f := range structs.Fields(v) {
if f.IsExported() && !f.IsZero() && f.Kind() != reflect.Ptr && f.Kind() != reflect.Slice && f.Kind() != reflect.Chan {
field := strings.ToLower(trueType + "_" + f.Name())
fieldKey[field] = replaceVal(f.Value())
}
}
fieldKey[strings.ToLower(trueType+"_pointer")] = fmt.Sprintf("%p", v)
}
return fieldKey
}
// replaceVal accepts an interface to be converted into human readable type
func replaceVal(d interface{}) interface{} {
switch v := d.(type) {
case types.NullBool:
return v.Bool
case types.NullString:
return v.String
case types.NullFloat64:
return v.Float64
case types.NullInt64:
return v.Int64
case string:
if len(v) > 500 {
return v[:500] + "... (truncated in logs)"
}
return v
case time.Time:
return v.String()
case time.Duration:
return v.String()
default:
return d
}
}
// createLog will create the '/logs' directory based on a directory // 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
@ -46,11 +159,6 @@ func createLog(dir string) error {
return err return err
} }
} }
file, err := os.Create(dir + "/logs/statup.log")
if err != nil {
return err
}
defer file.Close()
return err return err
} }
@ -60,77 +168,42 @@ func InitLogs() error {
if err != nil { if err != nil {
return err return err
} }
logFile, err = os.OpenFile(Directory+"/logs/statup.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
if err != nil {
log.Printf("ERROR opening file: %v", err)
return err
}
ljLogger = &lumberjack.Logger{ ljLogger = &lumberjack.Logger{
Filename: Directory + "/logs/statup.log", Filename: Directory + logFilePath,
MaxSize: 16, MaxSize: 16,
MaxBackups: 3, MaxBackups: 5,
MaxAge: 28, MaxAge: 28,
} }
fmtLogs = log.New(logFile, "", log.Ldate|log.Ltime) mw := io.MultiWriter(os.Stdout, ljLogger)
log.SetOutput(ljLogger) Log.SetOutput(mw)
rotate()
Log.SetFormatter(&Logger.TextFormatter{
ForceColors: true,
DisableColors: false,
})
Log.AddHook(new(hook))
Log.SetNoLock()
if VerboseMode == 1 {
Log.SetLevel(Logger.WarnLevel)
} else if VerboseMode == 2 {
Log.SetLevel(Logger.InfoLevel)
} else if VerboseMode == 3 {
Log.SetLevel(Logger.DebugLevel)
} else {
Log.SetReportCaller(true)
Log.SetLevel(Logger.TraceLevel)
}
LastLines = make([]*LogRow, 0) LastLines = make([]*LogRow, 0)
return err return err
} }
func rotate() { // CloseLogs will close the log file correctly on shutdown
c := make(chan os.Signal, 1) func CloseLogs() {
signal.Notify(c, syscall.SIGHUP) ljLogger.Rotate()
go func() { Log.Writer().Close()
for { ljLogger.Close()
<-c
ljLogger.Rotate()
}
}()
}
// Log creates a new entry in the Logger. Log has 1-5 levels depending on how critical the log/error is
func Log(level int, err interface{}) error {
if disableLogs {
return nil
}
pushLastLine(err)
var outErr error
switch level {
case 5:
_, outErr = fmt.Printf("PANIC: %v\n", err)
fmtLogs.Printf("PANIC: %v\n", err)
case 4:
_, outErr = fmt.Printf("FATAL: %v\n", err)
fmtLogs.Printf("FATAL: %v\n", err)
//color.Red("ERROR: %v\n", err)
//os.Exit(2)
case 3:
_, outErr = fmt.Printf("ERROR: %v\n", err)
fmtLogs.Printf("ERROR: %v\n", err)
//color.Red("ERROR: %v\n", err)
case 2:
_, outErr = fmt.Printf("WARNING: %v\n", err)
fmtLogs.Printf("WARNING: %v\n", err)
//color.Yellow("WARNING: %v\n", err)
case 1:
_, outErr = fmt.Printf("INFO: %v\n", err)
fmtLogs.Printf("INFO: %v\n", err)
//color.Blue("INFO: %v\n", err)
case 0:
_, outErr = fmt.Printf("%v\n", err)
fmtLogs.Printf("%v\n", err)
//color.White("%v\n", err)
}
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)
fmt.Printf("WEB: %v\n", msg)
pushLastLine(msg)
return msg
} }
func pushLastLine(line interface{}) { func pushLastLine(line interface{}) {

View File

@ -34,6 +34,11 @@ func Timezoner(t time.Time, zone float32) time.Time {
return timez return timez
} }
// Now returns the UTC timestamp
func Now() time.Time {
return time.Now().UTC()
}
// FormatDuration converts a time.Duration into a string // FormatDuration converts a time.Duration into a string
func FormatDuration(d time.Duration) string { func FormatDuration(d time.Duration) string {
var out string var out string

View File

@ -176,7 +176,7 @@ func FileExists(name string) bool {
// DeleteFile will attempt to delete a file // DeleteFile will attempt to delete a file
// DeleteFile("newfile.json") // DeleteFile("newfile.json")
func DeleteFile(file string) error { func DeleteFile(file string) error {
Log(1, "deleting file: "+file) Log.Infoln("deleting file: " + file)
err := os.Remove(file) err := os.Remove(file)
if err != nil { if err != nil {
return err return err
@ -193,7 +193,7 @@ func DeleteDirectory(directory string) error {
// Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings // Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings
// in, out, err := Command("sass assets/scss assets/css/base.css") // in, out, err := Command("sass assets/scss assets/css/base.css")
func Command(cmd string) (string, string, error) { func Command(cmd string) (string, string, error) {
Log(1, "running command: "+cmd) Log.Infoln("running command: " + cmd)
testCmd := exec.Command("sh", "-c", cmd) testCmd := exec.Command("sh", "-c", cmd)
var stdout, stderr []byte var stdout, stderr []byte
var errStdout, errStderr error var errStdout, errStderr error

View File

@ -16,7 +16,6 @@
package utils package utils
import ( import (
"errors"
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http" "net/http"
@ -42,7 +41,8 @@ func TestCreateLog(t *testing.T) {
func TestInitLogs(t *testing.T) { func TestInitLogs(t *testing.T) {
assert.Nil(t, InitLogs()) assert.Nil(t, InitLogs())
assert.FileExists(t, Directory+"/logs/statup.log") Log.Infoln("this is a test")
assert.FileExists(t, Directory+"/logs/statping.log")
} }
func TestDir(t *testing.T) { func TestDir(t *testing.T) {
@ -57,15 +57,6 @@ func TestCommand(t *testing.T) {
assert.Empty(t, out) assert.Empty(t, out)
} }
func TestLog(t *testing.T) {
assert.Nil(t, Log(0, errors.New("this is a 0 level error")))
assert.Nil(t, Log(1, errors.New("this is a 1 level error")))
assert.Nil(t, Log(2, errors.New("this is a 2 level error")))
assert.Nil(t, Log(3, errors.New("this is a 3 level error")))
assert.Nil(t, Log(4, errors.New("this is a 4 level error")))
assert.Nil(t, Log(5, errors.New("this is a 5 level error")))
}
func TestToInt(t *testing.T) { func TestToInt(t *testing.T) {
assert.Equal(t, int64(55), ToInt("55")) assert.Equal(t, int64(55), ToInt("55"))
assert.Equal(t, int64(55), ToInt(55)) assert.Equal(t, int64(55), ToInt(55))
@ -126,7 +117,7 @@ func ExampleDurationReadable() {
func TestLogHTTP(t *testing.T) { func TestLogHTTP(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil) req, err := http.NewRequest("GET", "/", nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotEmpty(t, Http(req)) assert.NotNil(t, req)
} }
func TestStringInt(t *testing.T) { func TestStringInt(t *testing.T) {

View File

@ -1 +1 @@
0.80.66 0.80.67