pull/429/head
Hunter Long 2020-03-04 02:29:00 -08:00
parent 29c332f0d9
commit 25913d6458
174 changed files with 4775 additions and 5466 deletions

View File

@ -11,6 +11,8 @@ TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statping
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
all: clean yarn-install compile docker-base docker-vue build-all compress
up:
docker-compose -f docker-compose.yml -f dev/docker-compose.full.yml up -d --remove-orphans
make print_details
@ -28,6 +30,9 @@ reup: down clean compose-build-full up
yarn-serve:
cd frontend && yarn serve
yarn-install:
cd frontend && rm -rf node_modules && yarn
go-run:
go run ./cmd
@ -122,21 +127,27 @@ print_details:
@echo \==== Monitoring and IDE ====
@echo \Grafana: http://localhost:3000 \(username: admin, password: admin\)
build-all: xgo-install build-mac build-linux build-linux build-alpine
download-key:
wget -O statping.gpg https://s3-us-west-2.amazonaws.com/assets.statping.com/UIDHJI2I292HDH20FJOIJOUIF29UHF827HHF9H2FHH27FGHRIEHFISUHFISHF.gpg
gpg --import statping.gpg
# build Statping for Mac, 64 and 32 bit
build-mac: compile
build-mac:
mkdir build
$(XGO) $(BUILDVERSION) --targets=darwin/amd64,darwin/386 ./cmd
# build Statping for Linux 64, 32 bit, arm6/arm7
build-linux: compile
build-linux:
$(XGO) $(BUILDVERSION) --targets=linux/amd64,linux/386,linux/arm-7,linux/arm-6,linux/arm64 ./cmd
# build for windows 64 bit only
build-windows: compile
build-windows:
$(XGO) $(BUILDVERSION) --targets=windows-6.0/amd64 ./cmd
# build Alpine linux binary (used in docker images)
build-alpine: compile
build-alpine:
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT) -linkmode external -extldflags -static" -out alpine ./cmd
# build :latest docker tag
@ -145,7 +156,7 @@ docker-build-latest:
docker tag hunterlong/statping:latest hunterlong/statping:v${VERSION}
# compress built binaries into tar.gz and zip formats
compress:
compress: download-key
cd build && mv alpine-linux-amd64 $(BINARY_NAME)
cd build && gpg --default-key $(SIGN_KEY) --batch --detach-sign --output statping.asc --armor $(BINARY_NAME)
cd build && tar -czvf $(BINARY_NAME)-linux-alpine.tar.gz $(BINARY_NAME) statping.asc && rm -f $(BINARY_NAME) statping.asc

30
cmd/assets.go Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"fmt"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils"
"net"
)
// UsingAssets will return true if /assets folder is present
func UsingAssets() bool {
return source.UsingAssets(utils.Directory)
}
// GetLocalIP returns the non loopback local IP of the host
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "http://localhost"
}
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return fmt.Sprintf("http://%v", ipnet.IP.String())
}
}
}
return "http://localhost"
}

View File

@ -18,8 +18,11 @@ package main
import (
"encoding/json"
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/handlers"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types/configs"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"github.com/joho/godotenv"
"github.com/pkg/errors"
@ -32,7 +35,6 @@ func catchCLI(args []string) error {
dir := utils.Directory
runLogs := utils.InitLogs
runAssets := source.Assets
loadDotEnvs()
switch args[0] {
case "version":
@ -100,14 +102,14 @@ func catchCLI(args []string) error {
if err = runAssets(); err != nil {
return err
}
configs, err := core.LoadConfigFile(dir)
config, err := configs.LoadConfigs()
if err != nil {
return err
}
if err = core.CoreApp.Connect(configs, false, dir); err != nil {
if err = config.Connect(); err != nil {
return err
}
if data, err = core.ExportSettings(); err != nil {
if data, err = handlers.ExportSettings(); err != nil {
return fmt.Errorf("could not export settings: %v", err.Error())
}
//core.CloseDB()
@ -125,7 +127,7 @@ func catchCLI(args []string) error {
if data, err = ioutil.ReadFile(filename); err != nil {
return err
}
var exportData core.ExportData
var exportData handlers.ExportData
if err = json.Unmarshal(data, &exportData); err != nil {
return err
}
@ -200,24 +202,27 @@ func updateDisplay() error {
// runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
func runOnce() error {
configs, err := core.LoadConfigFile(utils.Directory)
config, err := configs.LoadConfigs()
if err != nil {
return errors.Wrap(err, "config.yml file not found")
}
err = core.CoreApp.Connect(configs, false, utils.Directory)
err = config.Connect()
if err != nil {
return errors.Wrap(err, "issue connecting to database")
}
core.CoreApp, err = core.SelectCore()
c, err := core.Select()
if err != nil {
return errors.Wrap(err, "core database was not found or setup")
}
_, err = core.SelectAllServices(true)
core.App = c
_, err = services.SelectAllServices(true)
if err != nil {
return errors.Wrap(err, "could not select all services")
}
for _, srv := range core.Services() {
core.CheckService(srv, true)
for _, srv := range services.Services() {
services.CheckService(srv, true)
}
return nil
}

23
cmd/database.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"github.com/hunterlong/statping/types/checkins"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/groups"
"github.com/hunterlong/statping/types/hits"
"github.com/hunterlong/statping/types/incidents"
"github.com/hunterlong/statping/types/integrations"
"github.com/hunterlong/statping/types/messages"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/types/users"
)
var (
// DbSession stores the Statping database session
DbModels []interface{}
)
func init() {
DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, &notifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}, &integrations.Integration{}}
}

1
cmd/init.go Normal file
View File

@ -0,0 +1 @@
package main

View File

@ -16,15 +16,17 @@
package main
import (
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types/configs"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"github.com/pkg/errors"
"flag"
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/handlers"
"github.com/hunterlong/statping/source"
"github.com/joho/godotenv"
"os"
"os/signal"
"syscall"
@ -40,10 +42,11 @@ var (
verboseMode int
port int
log = utils.Log.WithField("type", "cmd")
httpServer = make(chan bool)
)
func init() {
core.VERSION = VERSION
}
// parseFlags will parse the application flags
@ -63,7 +66,6 @@ func parseFlags() {
}
func exit(err error) {
fmt.Printf("%+v", core.Configs())
panic(err)
//log.Fatalln(err)
//os.Exit(2)
@ -73,13 +75,19 @@ func exit(err error) {
func main() {
var err error
go sigterm()
parseFlags()
loadDotEnvs()
source.Assets()
if err := source.Assets(); err != nil {
exit(err)
}
utils.VerboseMode = verboseMode
if err := utils.InitLogs(); err != nil {
log.Errorf("Statping Log Error: %v\n", err)
}
args := flag.Args()
if len(args) >= 1 {
@ -98,41 +106,21 @@ func main() {
log.Warnln(err)
}
// check if DB_CONN was set, and load config from that
autoConfigDb := utils.Getenv("DB_CONN", "").(string)
if autoConfigDb != "" {
log.Infof("Environment variable 'DB_CONN' was set to %s, loading configs from ENV.", autoConfigDb)
if _, err := core.LoadUsingEnv(); err != nil {
exit(err)
return
} else {
afterConfigLoaded()
}
}
// attempt to load config.yml file from current directory, if no file, then start in setup mode.
_, err = core.LoadConfigFile(utils.Directory)
c, err := configs.LoadConfigs()
if err != nil {
log.Errorln(err)
core.CoreApp.Setup = false
writeAble, err := utils.DirWritable(utils.Directory)
if err != nil {
if err := SetupMode(); err != nil {
exit(err)
return
}
if !writeAble {
log.Fatalf("Statping does not have write permissions at: %v\nYou can change this directory by setting the STATPING_DIR environment variable to a dedicated path before starting.", utils.Directory)
return
}
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
log.Fatalln(err)
}
} else {
afterConfigLoaded()
}
}
func afterConfigLoaded() {
if err = c.Connect(); err != nil {
exit(err)
}
if err := configs.MigrateDatabase(); err != nil {
exit(err)
}
if err := mainProcess(); err != nil {
exit(err)
}
@ -141,7 +129,11 @@ func afterConfigLoaded() {
// Close will gracefully stop the database connection, and log file
func Close() {
utils.CloseLogs()
core.CloseDB()
database.Close()
}
func SetupMode() error {
return handlers.RunHTTPServer(ipAddress, port)
}
// sigterm will attempt to close the database connections gracefully
@ -153,30 +145,9 @@ func sigterm() {
os.Exit(1)
}
// loadDotEnvs attempts to load database configs from a '.env' file in root directory
func loadDotEnvs() error {
err := godotenv.Load(envFile)
if err == nil {
log.Infoln("Environment file '.env' Loaded")
}
return err
}
// mainProcess will initialize the Statping application and run the HTTP server
func mainProcess() error {
dir := utils.Directory
var err error
err = core.CoreApp.Connect(core.Configs(), false, dir)
if err != nil {
log.Errorln(fmt.Sprintf("could not connect to database: %v", err))
return err
}
if err := core.CoreApp.MigrateDatabase(); err != nil {
return errors.Wrap(err, "database migration")
}
if err := core.CoreApp.ServicesFromEnvFile(); err != nil {
if err := services.ServicesFromEnvFile(); err != nil {
errStr := "error 'SERVICE' environment variable"
log.Errorln(errStr)
return errors.Wrap(err, errStr)
@ -185,11 +156,33 @@ func mainProcess() error {
if err := core.InitApp(); err != nil {
return err
}
if core.CoreApp.Setup {
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
log.Fatalln(err)
return errors.Wrap(err, "http server")
}
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
log.Fatalln(err)
return errors.Wrap(err, "http server")
}
return nil
}
func StartHTTPServer() {
httpServer = make(chan bool)
go httpServerProcess(httpServer)
}
func StopHTTPServer() {
}
func httpServerProcess(process <-chan bool) {
for {
select {
case <-process:
fmt.Println("HTTP Server has stopped")
return
default:
if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
log.Errorln(err)
}
}
}
}

3
cmd/notifiers.go Normal file
View File

@ -0,0 +1,3 @@
package main
// AttachNotifiers will attach all the notifier's into the system

View File

@ -1,154 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"time"
)
type Checkin struct {
*database.CheckinObj
}
type CheckinHit struct {
*types.CheckinHit
}
// Select returns a *types.Checkin
func (c *Checkin) Select() *types.Checkin {
return c.Checkin
}
// Routine for checking if the last Checkin was within its interval
func CheckinRoutine(checkin database.Checkiner) {
c := checkin.Model()
if checkin.Hits().Last() == nil {
return
}
reCheck := c.Period()
CheckinLoop:
for {
select {
case <-c.Running:
log.Infoln(fmt.Sprintf("Stopping checkin routine: %v", c.Name))
c.Failing = false
break CheckinLoop
case <-time.After(reCheck):
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() <= 0 {
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, checkin.Hits().Last().CreatedAt)
log.Errorln(issue)
CreateCheckinFailure(checkin)
}
reCheck = c.Period()
}
continue
}
}
// String will return a Checkin API string
func (c *Checkin) String() string {
return c.ApiKey
}
func CreateCheckinFailure(checkin database.Checkiner) (int64, error) {
c := checkin.Model()
service := checkin.Service()
c.Failing = true
fail := &types.Failure{
Issue: fmt.Sprintf("Checkin %v was not reported %v ago, it expects a request every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())),
Method: "checkin",
MethodId: c.Id,
Service: service.Id,
Checkin: c.Id,
PingTime: c.Expected().Seconds(),
}
_, err := database.Create(fail)
if err != nil {
return 0, err
}
//sort.Sort(types.FailSort(c.Failures()))
return fail.Id, err
}
// AllCheckins returns all checkin in system
func AllCheckins() []*database.CheckinObj {
checkins := database.AllCheckins()
return checkins
}
// SelectCheckin will find a Checkin based on the API supplied
func SelectCheckin(api string) *Checkin {
for _, s := range Services() {
for _, c := range s.Checkins() {
if c.ApiKey == api {
return &Checkin{c}
}
}
}
return nil
}
// Create will create a new Checkin
func (c *Checkin) Delete() {
c.Close()
i := c.index()
srv := c.Service()
slice := srv.Service.Checkins
srv.Service.Checkins = append(slice[:i], slice[i+1:]...)
}
// index returns a checkin index int for updating the *checkin.Service slice
func (c *Checkin) index() int {
for k, checkin := range c.Service().Checkins() {
if c.Id == checkin.Model().Id {
return k
}
}
return 0
}
// Create will create a new Checkin
func (c *Checkin) Create() (int64, error) {
c.ApiKey = utils.RandomString(7)
_, err := database.Create(c)
if err != nil {
log.Warnln(err)
return 0, err
}
c.Start()
go CheckinRoutine(c)
return c.Id, err
}
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
between := utils.Now().Sub(utils.Now()).Seconds()
if between > float64(c.Interval) {
fmt.Println("rechecking every 15 seconds!")
time.Sleep(15 * time.Second)
guard <- struct{}{}
c.RecheckCheckinFailure(guard)
} else {
fmt.Println("i recovered!!")
}
<-guard
}

View File

@ -1,216 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"github.com/go-yaml/yaml"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/pkg/errors"
)
// ErrorResponse is used for HTTP errors to show to User
type ErrorResponse struct {
Error string
}
// LoadConfigFile will attempt to load the 'config.yml' file in a specific directory
func LoadConfigFile(directory string) (*types.DbConfig, error) {
var configs *types.DbConfig
log.Debugln("Attempting to read config file at: " + directory + "/config.yml")
file, err := utils.OpenFile(directory + "/config.yml")
if err != nil {
CoreApp.Setup = false
return nil, errors.Wrapf(err, "config.yml file not found at %s/config.yml - starting in setup mode", directory)
}
err = yaml.Unmarshal([]byte(file), &configs)
if err != nil {
return nil, errors.Wrap(err, "yaml file not formatted correctly")
}
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + directory + "/config.yml")
CoreApp.config = configs
return configs, err
}
func Configs() *types.DbConfig {
return CoreApp.config
}
// LoadUsingEnv will attempt to load database configs based on environment variables. If DB_CONN is set if will force this function.
func LoadUsingEnv() (*types.DbConfig, error) {
configs, err := EnvToConfig()
if err != nil {
return configs, err
}
CoreApp.Name = utils.Getenv("NAME", "").(string)
CoreApp.Domain = utils.Getenv("DOMAIN", "").(string)
CoreApp.UseCdn = types.NewNullBool(utils.Getenv("USE_CDN", false).(bool))
err = CoreApp.Connect(configs, true, utils.Directory)
if err != nil {
return nil, errors.Wrap(err, "error connecting to database")
}
if err := SaveConfig(configs); err != nil {
return nil, errors.Wrap(err, "error saving configuration")
}
exists := database.Get().HasTable("core")
if !exists {
return InitialSetup(configs)
}
CoreApp.config = configs
return configs, nil
}
func InitialSetup(configs *types.DbConfig) (*types.DbConfig, error) {
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
if err := CoreApp.DropDatabase(); err != nil {
return nil, errors.Wrap(err, "error dropping database")
}
if err := CoreApp.CreateDatabase(); err != nil {
return nil, errors.Wrap(err, "error creating database")
}
CoreApp, err := InsertCore(configs)
if err != nil {
return nil, errors.Wrap(err, "error creating the core database")
}
username := utils.Getenv("ADMIN_USER", "admin").(string)
password := utils.Getenv("ADMIN_PASSWORD", "admin").(string)
admin := &types.User{
Username: username,
Password: utils.HashPassword(password),
Email: "info@admin.com",
Admin: types.NewNullBool(true),
}
if _, err := database.Create(admin); err != nil {
return nil, errors.Wrap(err, "error creating admin")
}
if err := SampleData(); err != nil {
return nil, errors.Wrap(err, "error connecting sample data")
}
CoreApp.config = configs
return configs, err
}
// defaultPort accepts a database type and returns its default port
func defaultPort(db string) int {
switch db {
case "mysql":
return 3306
case "postgres":
return 5432
case "mssql":
return 1433
default:
return 0
}
}
// EnvToConfig converts environment variables to a DbConfig
func EnvToConfig() (*types.DbConfig, error) {
var err error
dbConn := utils.Getenv("DB_CONN", "").(string)
dbHost := utils.Getenv("DB_HOST", "").(string)
dbUser := utils.Getenv("DB_USER", "").(string)
dbPass := utils.Getenv("DB_PASS", "").(string)
dbData := utils.Getenv("DB_DATABASE", "").(string)
dbPort := utils.Getenv("DB_PORT", defaultPort(dbConn)).(int)
name := utils.Getenv("NAME", "Statping").(string)
desc := utils.Getenv("DESCRIPTION", "Statping Monitoring Sample Data").(string)
user := utils.Getenv("ADMIN_USER", "admin").(string)
password := utils.Getenv("ADMIN_PASS", "admin").(string)
domain := utils.Getenv("DOMAIN", "").(string)
sqlFile := utils.Getenv("SQL_FILE", "").(string)
if dbConn != "" && dbConn != "sqlite" {
if dbHost == "" {
return nil, errors.New("Missing DB_HOST environment variable")
}
if dbPort == 0 {
return nil, errors.New("Missing DB_PORT environment variable")
}
if dbUser == "" {
return nil, errors.New("Missing DB_USER environment variable")
}
if dbPass == "" {
return nil, errors.New("Missing DB_PASS environment variable")
}
if dbData == "" {
return nil, errors.New("Missing DB_DATABASE environment variable")
}
}
config := &types.DbConfig{
DbConn: dbConn,
DbHost: dbHost,
DbUser: dbUser,
DbPass: dbPass,
DbData: dbData,
DbPort: dbPort,
Project: name,
Description: desc,
Domain: domain,
Email: "",
Username: user,
Password: password,
Error: nil,
Location: utils.Directory,
SqlFile: sqlFile,
}
return config, err
}
// SampleData runs all the sample data for a new Statping installation
func SampleData() error {
if err := InsertSampleData(); err != nil {
return errors.Wrap(err, "sample data")
}
if err := InsertSampleHits(); err != nil {
return errors.Wrap(err, "sample service hits")
}
if err := insertSampleCheckins(); err != nil {
return errors.Wrap(err, "sample checkin examples")
}
return nil
}
// DeleteConfig will delete the 'config.yml' file
func DeleteConfig() error {
log.Debugln("deleting config yaml file", utils.Directory+"/config.yml")
err := utils.DeleteFile(utils.Directory + "/config.yml")
if err != nil {
return errors.Wrap(err, "error deleting config.yml")
}
return nil
}
func IsSetup() bool {
if CoreApp.config != nil {
return true
}
return false
}

View File

@ -1,175 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"fmt"
"github.com/hunterlong/statping/core/integrations"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"net"
"os"
"time"
)
type PluginJSON types.PluginJSON
type PluginRepos types.PluginRepos
type Core struct {
*types.Core
services map[int64]*Service
config *types.DbConfig
}
var (
CoreApp *Core // CoreApp is a global variable that contains many elements
VERSION string // VERSION is set on build automatically by setting a -ldflag
log = utils.Log.WithField("type", "core")
)
func init() {
CoreApp = NewCore()
}
// NewCore return a new *core.Core struct
func NewCore() *Core {
CoreApp = &Core{Core: &types.Core{
Started: time.Now().UTC(),
}}
CoreApp.services = make(map[int64]*Service)
return CoreApp
}
// ToCore will convert *core.Core to *types.Core
func (c *Core) ToCore() *types.Core {
return c.Core
}
// InitApp will initialize Statping
func InitApp() error {
if _, err := SelectCore(); err != nil {
return err
}
if err := InsertNotifierDB(); err != nil {
return err
}
if err := InsertIntegratorDB(); err != nil {
return err
}
if _, err := SelectAllServices(true); err != nil {
return err
}
if err := AttachNotifiers(); err != nil {
return err
}
CoreApp.Notifications = notifier.AllCommunications
if err := AddIntegrations(); err != nil {
return err
}
CoreApp.Integrations = integrations.Integrations
go checkServices()
database.StartMaintenceRoutine()
CoreApp.Setup = true
return nil
}
// InsertNotifierDB inject the Statping database instance to the Notifier package
func InsertNotifierDB() error {
if !database.Available() {
err := CoreApp.Connect(CoreApp.config, false, utils.Directory)
if err != nil {
return errors.New("database connection has not been created")
}
}
notifier.SetDB(database.Get())
return nil
}
// InsertIntegratorDB inject the Statping database instance to the Integrations package
func InsertIntegratorDB() error {
if !database.Available() {
err := CoreApp.Connect(CoreApp.config, false, utils.Directory)
if err != nil {
return errors.New("database connection has not been created")
}
}
integrations.SetDB(database.Get())
return nil
}
// UsingAssets will return true if /assets folder is present
func (c Core) UsingAssets() bool {
return source.UsingAssets(utils.Directory)
}
// SelectCore will return the CoreApp global variable and the settings/configs for Statping
func SelectCore() (*Core, error) {
if !database.Available() {
log.Traceln("database has not been initiated yet.")
return nil, errors.New("database has not been initiated yet.")
}
exists := database.Get().HasTable("core")
if !exists {
log.Errorf("core database has not been setup yet, does not have the 'core' table")
return nil, errors.New("core database has not been setup yet.")
}
db := database.Core().First(&CoreApp)
if db.Error() != nil {
return nil, db.Error()
}
CoreApp.Version = VERSION
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
return CoreApp, db.Error()
}
// GetLocalIP returns the non loopback local IP of the host
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "http://localhost"
}
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return fmt.Sprintf("http://%v", ipnet.IP.String())
}
}
}
return "http://localhost"
}
// ServiceOrder will reorder the services based on 'order_id' (Order)
type ServiceOrder map[int64]*Service
// Sort interface for resroting the Services in order
func (c ServiceOrder) Len() int { return len(c) }
func (c ServiceOrder) Swap(i, j int) { c[int64(i)], c[int64(j)] = c[int64(j)], c[int64(i)] }
func (c ServiceOrder) Less(i, j int) bool {
if c[int64(i)] == nil {
return false
}
if c[int64(j)] == nil {
return false
}
return c[int64(i)].Order < c[int64(j)].Order
}

View File

@ -1,151 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
)
var (
dir string
skipNewDb bool
)
var configs *types.DbConfig
func init() {
dir = utils.Directory
utils.InitLogs()
source.Assets()
skipNewDb = false
SampleHits = 480
}
func TestNewCore(t *testing.T) {
err := TmpRecords("core.db")
t.Log(err)
require.Nil(t, err)
require.NotNil(t, CoreApp)
}
func TestDbConfig_Save(t *testing.T) {
if skipNewDb {
t.SkipNow()
}
config := &types.DbConfig{
DbConn: "sqlite",
Project: "Tester",
Location: dir,
}
err := SaveConfig(config)
require.Nil(t, err)
assert.Equal(t, "sqlite", config.DbConn)
assert.NotEmpty(t, config.ApiKey)
assert.NotEmpty(t, config.ApiSecret)
}
func TestLoadDbConfig(t *testing.T) {
Configs, err := LoadConfigFile(dir)
assert.Nil(t, err)
assert.Equal(t, "sqlite", Configs.DbConn)
configs = Configs
}
func TestDbConnection(t *testing.T) {
err := CoreApp.Connect(configs, false, dir)
assert.Nil(t, err)
}
func TestDropDatabase(t *testing.T) {
if skipNewDb {
t.SkipNow()
}
err := CoreApp.DropDatabase()
assert.Nil(t, err)
}
func TestSeedSchemaDatabase(t *testing.T) {
if skipNewDb {
t.SkipNow()
}
err := CoreApp.CreateDatabase()
assert.Nil(t, err)
}
func TestMigrateDatabase(t *testing.T) {
t.SkipNow()
err := CoreApp.MigrateDatabase()
assert.Nil(t, err)
}
func TestSeedDatabase(t *testing.T) {
err := InsertLargeSampleData()
assert.Nil(t, err)
}
func TestReLoadDbConfig(t *testing.T) {
err := CoreApp.Connect(configs, false, dir)
assert.Nil(t, err)
assert.Equal(t, "sqlite", CoreApp.config.DbConn)
}
func TestSelectCore(t *testing.T) {
core, err := SelectCore()
assert.Nil(t, err)
assert.Equal(t, "Statping Sample Data", core.Name)
}
func TestInsertNotifierDB(t *testing.T) {
if skipNewDb {
t.SkipNow()
}
err := InsertNotifierDB()
assert.Nil(t, err)
}
func TestEnvToConfig(t *testing.T) {
os.Setenv("DB_CONN", "sqlite")
os.Setenv("DB_USER", "")
os.Setenv("DB_PASS", "")
os.Setenv("DB_DATABASE", "")
os.Setenv("NAME", "Testing")
os.Setenv("DOMAIN", "http://localhost:8080")
os.Setenv("DESCRIPTION", "Testing Statping")
os.Setenv("ADMIN_USER", "admin")
os.Setenv("ADMIN_PASS", "admin123")
os.Setenv("VERBOSE", "1")
config, err := EnvToConfig()
assert.Nil(t, err)
assert.Equal(t, config.DbConn, "sqlite")
assert.Equal(t, config.Domain, "http://localhost:8080")
assert.Equal(t, config.Description, "Testing Statping")
assert.Equal(t, config.Username, "admin")
assert.Equal(t, config.Password, "admin123")
}
func TestGetLocalIP(t *testing.T) {
ip := GetLocalIP()
assert.Contains(t, ip, "http://")
}

View File

@ -1,410 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"bufio"
"fmt"
"github.com/pkg/errors"
"os"
"path/filepath"
"time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/go-yaml/yaml"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/jinzhu/gorm"
)
var (
// DbSession stores the Statping database session
DbModels []interface{}
)
func init() {
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, &notifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}, &types.Integration{}}
}
// CloseDB will close the database connection if available
func CloseDB() {
database.Close()
}
//// AfterFind for Core will set the timezone
//func (c *Core) AfterFind() (err error) {
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
// c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for Service will set the timezone
//func (s *Service) AfterFind() (err error) {
// s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone)
// s.UpdatedAt = utils.Timezoner(s.UpdatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for Hit will set the timezone
//func (h *Hit) AfterFind() (err error) {
// h.CreatedAt = utils.Timezoner(h.CreatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for Failure will set the timezone
//func (f *Failure) AfterFind() (err error) {
// f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for USer will set the timezone
//func (u *User) AfterFind() (err error) {
// u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
// u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for Checkin will set the timezone
//func (c *Checkin) AfterFind() (err error) {
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
// c.UpdatedAt = utils.Timezoner(c.UpdatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for checkinHit will set the timezone
//func (c *CheckinHit) AfterFind() (err error) {
// c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
// return
//}
//
//// AfterFind for Message will set the timezone
//func (u *Message) AfterFind() (err error) {
// u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
// u.UpdatedAt = utils.Timezoner(u.UpdatedAt, CoreApp.Timezone)
// u.StartOn = utils.Timezoner(u.StartOn.UTC(), CoreApp.Timezone)
// u.EndOn = utils.Timezoner(u.EndOn.UTC(), CoreApp.Timezone)
// return
//}
// InsertCore create the single row for the Core settings in Statping
func InsertCore(d *types.DbConfig) (*Core, error) {
apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(40))
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(40))
core := &types.Core{
Name: d.Project,
Description: d.Description,
ConfigFile: "config.yml",
ApiKey: apiKey.(string),
ApiSecret: apiSecret.(string),
Domain: d.Domain,
MigrationId: time.Now().Unix(),
}
CoreApp = &Core{Core: core}
CoreApp.config = d
_, err := database.Create(CoreApp.Core)
return CoreApp, err
}
func findDbFile() string {
if CoreApp.config == nil {
return findSQLin(utils.Directory)
}
if CoreApp.config.SqlFile != "" {
return CoreApp.config.SqlFile
}
return utils.Directory + "/" + types.SqliteFilename
}
func findSQLin(path string) string {
filename := types.SqliteFilename
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if filepath.Ext(path) == ".db" {
fmt.Println("DB file is now: ", info.Name())
filename = info.Name()
}
return nil
})
if err != nil {
log.Error(err)
}
return filename
}
// Connect will attempt to connect to the sqlite, postgres, or mysql database
func (c *Core) Connect(configs *types.DbConfig, retry bool, location string) error {
postgresSSL := os.Getenv("POSTGRES_SSLMODE")
if database.Available() {
return nil
}
var conn string
var err error
switch configs.DbConn {
case "sqlite", "sqlite3":
conn = findDbFile()
configs.SqlFile = fmt.Sprintf("%s/%s", utils.Directory, conn)
log.Infof("SQL database file at: %s", configs.SqlFile)
configs.DbConn = "sqlite3"
case "mysql":
host := fmt.Sprintf("%v:%v", configs.DbHost, configs.DbPort)
conn = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8&parseTime=True&loc=UTC&time_zone=%%27UTC%%27", configs.DbUser, configs.DbPass, host, configs.DbData)
case "postgres":
sslMode := "disable"
if postgresSSL != "" {
sslMode = postgresSSL
}
conn = fmt.Sprintf("host=%v port=%v user=%v dbname=%v password=%v timezone=UTC sslmode=%v", configs.DbHost, configs.DbPort, configs.DbUser, configs.DbData, configs.DbPass, sslMode)
case "mssql":
host := fmt.Sprintf("%v:%v", configs.DbHost, configs.DbPort)
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", configs.DbUser, configs.DbPass, host, configs.DbData)
}
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
dbSession, err := database.Openw(configs.DbConn, conn)
if err != nil {
log.Debugln(fmt.Sprintf("Database connection error %s", err))
if retry {
log.Errorln(fmt.Sprintf("Database %s connection to '%s' is not available, trying again in 5 seconds...", configs.DbConn, configs.DbHost))
return c.waitForDb(configs)
} else {
return err
}
}
log.WithFields(utils.ToFields(dbSession)).Debugln("connected to database")
maxOpenConn := utils.Getenv("MAX_OPEN_CONN", 5)
maxIdleConn := utils.Getenv("MAX_IDLE_CONN", 5)
maxLifeConn := utils.Getenv("MAX_LIFE_CONN", 2*time.Minute)
dbSession.DB().SetMaxOpenConns(maxOpenConn.(int))
dbSession.DB().SetMaxIdleConns(maxIdleConn.(int))
dbSession.DB().SetConnMaxLifetime(maxLifeConn.(time.Duration))
if dbSession.DB().Ping() == nil {
if utils.VerboseMode >= 4 {
database.LogMode(true).Debug().SetLogger(gorm.Logger{log})
}
log.Infoln(fmt.Sprintf("Database %v connection was successful.", configs.DbConn))
}
CoreApp.config = configs
return err
}
// waitForDb will sleep for 5 seconds and try to connect to the database again
func (c *Core) waitForDb(configs *types.DbConfig) error {
time.Sleep(5 * time.Second)
return c.Connect(configs, true, utils.Directory)
}
// Update will save the config.yml file
func (c *Core) UpdateConfig() error {
var err error
config, err := os.Create(utils.Directory + "/config.yml")
if err != nil {
log.Errorln(err)
return err
}
defer config.Close()
data, err := yaml.Marshal(c.config)
if err != nil {
log.Errorln(err)
return err
}
config.WriteString(string(data))
return err
}
// Save will initially create the config.yml file
func SaveConfig(d *types.DbConfig) error {
config, err := os.Create(utils.Directory + "/config.yml")
if err != nil {
log.Errorln(err)
return err
}
defer config.Close()
log.WithFields(utils.ToFields(d)).Debugln("saving config file at: " + utils.Directory + "/config.yml")
if d.ApiKey == "" || d.ApiSecret == "" {
apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(16))
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(16))
d.ApiKey = apiKey.(string)
d.ApiSecret = apiSecret.(string)
}
if d.DbConn == "sqlite3" {
d.DbConn = "sqlite"
}
data, err := yaml.Marshal(d)
if err != nil {
log.Errorln(err)
return err
}
if _, err := config.WriteString(string(data)); err != nil {
return errors.Wrap(err, "error writing to config.yml")
}
log.WithFields(utils.ToFields(d)).Infoln("Saved config file at: " + utils.Directory + "/config.yml")
CoreApp.config = d
return err
}
// CreateCore will initialize the global variable 'CoreApp". This global variable contains most of Statping app.
func (c *Core) CreateCore() *Core {
newCore := &types.Core{
Name: c.Name,
Description: c.Description,
ConfigFile: utils.Directory + "/config.yml",
ApiKey: c.ApiKey,
ApiSecret: c.ApiSecret,
Domain: c.Domain,
MigrationId: time.Now().Unix(),
}
_, err := database.Create(&newCore)
if err == nil {
CoreApp = &Core{Core: newCore}
}
CoreApp, err := SelectCore()
if err != nil {
log.Errorln(err)
}
return CoreApp
}
// DropDatabase will DROP each table Statping created
func (c *Core) DropDatabase() error {
log.Infoln("Dropping Database Tables...")
for _, t := range DbModels {
if err := database.Get().DropTableIfExists(t); err != nil {
return err.Error()
}
log.Infof("Dropped table: %T\n", t)
}
return nil
}
// CreateDatabase will CREATE TABLES for each of the Statping elements
func (c *Core) CreateDatabase() error {
var err error
log.Infoln("Creating Database Tables...")
for _, table := range DbModels {
if err := database.Get().CreateTable(table); err.Error() != nil {
return err.Error()
}
}
if err := database.Get().Table("core").CreateTable(&types.Core{}); err.Error() != nil {
return err.Error()
}
log.Infoln("Statping Database Created")
return err
}
// findServiceByHas will return a service that matches the SHA256 hash of a service
// Service hash example: sha256(name:EXAMPLEdomain:HTTP://DOMAIN.COMport:8080type:HTTPmethod:GET)
func findServiceByHash(hash string) *Service {
for _, service := range Services() {
if service.String() == hash {
return service
}
}
return nil
}
func (c *Core) ServicesFromEnvFile() error {
servicesEnv := utils.Getenv("SERVICES_FILE", "").(string)
if servicesEnv == "" {
return nil
}
file, err := os.Open(servicesEnv)
if err != nil {
return errors.Wrapf(err, "error opening 'SERVICES_FILE' at: %s", servicesEnv)
}
defer file.Close()
var serviceLines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
serviceLines = append(serviceLines, scanner.Text())
}
if len(serviceLines) == 0 {
return nil
}
for k, service := range serviceLines {
svr, err := utils.ValidateService(service)
if err != nil {
return errors.Wrapf(err, "invalid service at index %d in SERVICES_FILE environment variable", k)
}
if findServiceByHash(svr.String()) == nil {
if _, err := database.Create(svr); err != nil {
return errors.Wrapf(err, "could not create service %s", svr.Name)
}
log.Infof("Created new service '%s'", svr.Name)
}
}
return nil
}
// MigrateDatabase will migrate the database structure to current version.
// This function will NOT remove previous records, tables or columns from the database.
// If this function has an issue, it will ROLLBACK to the previous state.
func (c *Core) MigrateDatabase() error {
log.Infoln("Migrating Database Tables...")
tx := database.Begin("migration")
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if tx.Error() != nil {
log.Errorln(tx.Error())
return tx.Error()
}
for _, table := range DbModels {
tx = tx.AutoMigrate(table)
}
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error() != nil {
tx.Rollback()
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error()))
return tx.Error()
}
if err := tx.Commit().Error(); err != nil {
return err
}
log.Infoln("Statping Database Migrated")
return nil
}

View File

@ -1,6 +0,0 @@
// Package core contains the main functionality of Statping. This includes everything for
// Services, Hits, Failures, Users, service checking mechanisms, databases, and notifiers
// in the notifier package
//
// More info on: https://github.com/hunterlong/statping
package core

View File

@ -1,29 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"github.com/hunterlong/statping/types"
)
type Failure struct {
*types.Failure
}
const (
limitedFailures = 32
limitedHits = 32
)

View File

@ -1,58 +0,0 @@
package core
import (
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"sort"
)
type Group struct {
*types.Group
}
// SelectGroups returns all groups
func SelectGroups(includeAll bool, auth bool) map[int64]*Group {
validGroups := make(map[int64]*Group)
groups := database.AllGroups()
for _, g := range groups {
if !g.Public.Bool {
if auth {
validGroups[g.Id] = &Group{g.Group}
}
} else {
validGroups[g.Id] = &Group{g.Group}
}
}
sort.Sort(GroupOrder(validGroups))
//if includeAll {
// validGroups = append(validGroups, &Group{})
//}
return validGroups
}
// SelectGroup returns a *core.Group
func SelectGroup(id int64) *Group {
groups := SelectGroups(true, true)
if groups[id] != nil {
return groups[id]
}
return nil
}
// GroupOrder will reorder the groups based on 'order_id' (Order)
type GroupOrder map[int64]*Group
// Sort interface for resorting the Groups in order
func (c GroupOrder) Len() int { return len(c) }
func (c GroupOrder) Swap(i, j int) { c[int64(i)], c[int64(j)] = c[int64(j)], c[int64(i)] }
func (c GroupOrder) Less(i, j int) bool {
if c[int64(i)] == nil {
return false
}
if c[int64(j)] == nil {
return false
}
return c[int64(i)].Order < c[int64(j)].Order
}

View File

@ -1,18 +0,0 @@
package core
import (
"github.com/hunterlong/statping/types"
)
type Incident struct {
*types.Incident
}
type IncidentUpdate struct {
*types.IncidentUpdate
}
// ReturnIncident returns *core.Incident based off a *types.Incident
func ReturnIncident(u *types.Incident) *Incident {
return &Incident{u}
}

View File

@ -1,12 +0,0 @@
package core
import "github.com/hunterlong/statping/core/integrations"
// AddIntegrations will attach all the integrations into the system
func AddIntegrations() error {
return integrations.AddIntegrations(
integrations.CsvIntegrator,
integrations.TraefikIntegrator,
integrations.DockerIntegrator,
)
}

View File

@ -1,29 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"github.com/hunterlong/statping/types"
)
type Message struct {
*types.Message
}
// ReturnMessage will convert *types.Message to *core.Message
func ReturnMessage(m *types.Message) *Message {
return &Message{m}
}

View File

@ -1,45 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package notifier
import (
"fmt"
"strings"
)
var (
allowedVars = []string{"host", "username", "password", "port", "api_key", "api_secret", "var1", "var2"}
)
func checkNotifierForm(n Notifier) error {
notifier := n.Select()
for _, f := range notifier.Form {
contains := contains(f.DbField, allowedVars)
if !contains {
return fmt.Errorf("the DbField '%v' is not allowed, allowed vars: %v", f.DbField, allowedVars)
}
}
return nil
}
func contains(s string, arr []string) bool {
for _, v := range arr {
if strings.ToLower(s) == v {
return true
}
}
return false
}

View File

@ -1,21 +0,0 @@
package core
import (
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/notifiers"
)
// AttachNotifiers will attach all the notifier's into the system
func AttachNotifiers() error {
return notifier.AddNotifiers(
notifiers.Command,
notifiers.Discorder,
notifiers.Emailer,
notifiers.LineNotify,
notifiers.Mobile,
notifiers.Slacker,
notifiers.Telegram,
notifiers.Twilio,
notifiers.Webhook,
)
}

View File

@ -1,726 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"sync"
"time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
var (
sampleStart = time.Now().Add((-24 * 7) * time.Hour).UTC()
SampleHits = 9900.
)
// InsertSampleData will create the example/dummy services for a brand new Statping installation
func InsertSampleData() error {
log.Infoln("Inserting Sample Data...")
if err := insertSampleGroups(); err != nil {
return err
}
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
s1 := &types.Service{
Name: "Google",
Domain: "https://google.com",
ExpectedStatus: 200,
Interval: 10,
Type: "http",
Method: "GET",
Timeout: 10,
Order: 1,
GroupId: 1,
Permalink: types.NewNullString("google"),
VerifySSL: types.NewNullBool(true),
CreatedAt: createdOn,
}
s2 := &types.Service{
Name: "Statping Github",
Domain: "https://github.com/hunterlong/statping",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 20,
Order: 2,
Permalink: types.NewNullString("statping_github"),
VerifySSL: types.NewNullBool(true),
CreatedAt: createdOn,
}
s3 := &types.Service{
Name: "JSON Users Test",
Domain: "https://jsonplaceholder.typicode.com/users",
ExpectedStatus: 200,
Interval: 60,
Type: "http",
Method: "GET",
Timeout: 30,
Order: 3,
Public: types.NewNullBool(true),
VerifySSL: types.NewNullBool(true),
GroupId: 2,
CreatedAt: createdOn,
}
s4 := &types.Service{
Name: "JSON API Tester",
Domain: "https://jsonplaceholder.typicode.com/posts",
ExpectedStatus: 201,
Expected: types.NewNullString(`(title)": "((\\"|[statping])*)"`),
Interval: 30,
Type: "http",
Method: "POST",
PostData: types.NewNullString(`{ "title": "statping", "body": "bar", "userId": 19999 }`),
Timeout: 30,
Order: 4,
Public: types.NewNullBool(true),
VerifySSL: types.NewNullBool(true),
GroupId: 2,
CreatedAt: createdOn,
}
s5 := &types.Service{
Name: "Google DNS",
Domain: "8.8.8.8",
Interval: 20,
Type: "tcp",
Port: 53,
Timeout: 120,
Order: 5,
Public: types.NewNullBool(true),
GroupId: 1,
CreatedAt: createdOn,
}
if _, err := database.Create(s1); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
log.Infof("Created Service '%s'", s1.Name)
if _, err := database.Create(s2); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
log.Infof("Created Service '%s'", s2.Name)
if _, err := database.Create(s3); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
log.Infof("Created Service '%s'", s3.Name)
if _, err := database.Create(s4); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
log.Infof("Created Service '%s'", s4.Name)
if _, err := database.Create(s5); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
log.Infof("Created Service '%s'", s5.Name)
if _, err := SelectAllServices(false); err != nil {
return types.ErrWrap(err, types.ErrorServiceSelection)
}
if err := insertMessages(); err != nil {
return types.ErrWrap(err, types.ErrorCreateMessage)
}
if err := insertSampleIncidents(); err != nil {
return types.ErrWrap(err, types.ErrorCreateIncident)
}
log.Infoln("Sample data has finished importing")
return nil
}
func insertSampleIncidents() error {
incident1 := &types.Incident{
Title: "Github Downtime",
Description: "This is an example of a incident for a service.",
ServiceId: 2,
}
if _, err := database.Create(incident1); err != nil {
return types.ErrWrap(err, types.ErrorCreateIncidentUp)
}
incidentUpdate1 := &types.IncidentUpdate{
IncidentId: incident1.Id,
Message: "Github's page for Statping seems to be sending a 501 error.",
Type: "Investigating",
}
if _, err := database.Create(incidentUpdate1); err != nil {
return types.ErrWrap(err, types.ErrorCreateIncidentUp)
}
incidentUpdate2 := &types.IncidentUpdate{
IncidentId: incident1.Id,
Message: "Problem is continuing and we are looking at the issues.",
Type: "Update",
}
if _, err := database.Create(incidentUpdate2); err != nil {
return types.ErrWrap(err, types.ErrorCreateIncidentUp)
}
incidentUpdate3 := &types.IncidentUpdate{
IncidentId: incident1.Id,
Message: "Github is now back online and everything is working.",
Type: "Resolved",
}
if _, err := database.Create(incidentUpdate3); err != nil {
return types.ErrWrap(err, types.ErrorCreateIncidentUp)
}
return nil
}
func insertSampleGroups() error {
group1 := &types.Group{
Name: "Main Services",
Public: types.NewNullBool(true),
Order: 2,
}
if _, err := database.Create(group1); err != nil {
return types.ErrWrap(err, types.ErrorCreateGroup)
}
group2 := &types.Group{
Name: "Linked Services",
Public: types.NewNullBool(false),
Order: 1,
}
if _, err := database.Create(group2); err != nil {
return types.ErrWrap(err, types.ErrorCreateGroup)
}
group3 := &types.Group{
Name: "Empty Group",
Public: types.NewNullBool(false),
Order: 3,
}
if _, err := database.Create(group3); err != nil {
return types.ErrWrap(err, types.ErrorCreateGroup)
}
return nil
}
// insertSampleCheckins will create 2 checkins with 60 successful hits per Checkin
func insertSampleCheckins() error {
s1 := SelectService(1)
checkin1 := &types.Checkin{
Name: "Example Checkin 1",
ServiceId: s1.Id,
Interval: 300,
GracePeriod: 300,
ApiKey: utils.RandomString(7),
}
if _, err := database.Create(checkin1); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s2 := SelectService(1)
checkin2 := &types.Checkin{
Name: "Example Checkin 2",
ServiceId: s2.Id,
Interval: 900,
GracePeriod: 300,
ApiKey: utils.RandomString(7),
}
if _, err := database.Create(checkin2); err != nil {
return types.ErrWrap(err, types.ErrorCreateCheckinHit)
}
checkTime := time.Now().UTC().Add(-24 * time.Hour)
for i := 0; i <= 60; i++ {
checkHit := &types.CheckinHit{
Checkin: checkin1.Id,
From: "192.168.0.1",
CreatedAt: checkTime.UTC(),
}
if _, err := database.Create(checkHit); err != nil {
return types.ErrWrap(err, types.ErrorCreateCheckinHit)
}
checkTime = checkTime.Add(10 * time.Minute)
}
return nil
}
// InsertSampleHits will create a couple new hits for the sample services
func InsertSampleHits() error {
tx := database.Begin(&types.Hit{})
sg := new(sync.WaitGroup)
for i := int64(1); i <= 5; i++ {
sg.Add(1)
service := SelectService(i)
seed := time.Now().UnixNano()
log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
createdAt := sampleStart
p := utils.NewPerlin(2., 2., 10, seed)
go func(sg *sync.WaitGroup) {
defer sg.Done()
for hi := 0.; hi <= float64(SampleHits); hi++ {
latency := p.Noise1D(hi / 500)
createdAt = createdAt.Add(60 * time.Second)
hit := &types.Hit{
Service: service.Id,
CreatedAt: createdAt,
Latency: latency,
}
tx = tx.Create(&hit)
}
}(sg)
}
sg.Wait()
if err := tx.Commit().Error(); err != nil {
log.Errorln(err)
return types.ErrWrap(err, types.ErrorCreateSampleHits)
}
return nil
}
// insertSampleCore will create a new Core for the seed
func insertSampleCore() error {
apiKey := utils.Getenv("API_KEY", "samplekey")
apiSecret := utils.Getenv("API_SECRET", "samplesecret")
core := &types.Core{
Name: "Statping Sample Data",
Description: "This data is only used to testing",
ApiKey: apiKey.(string),
ApiSecret: apiSecret.(string),
Domain: "http://localhost:8080",
Version: "test",
CreatedAt: time.Now().UTC(),
UseCdn: types.NewNullBool(false),
Footer: types.NewNullString(""),
}
if _, err := database.Create(core); err != nil {
return types.ErrWrap(err, types.ErrorCreateCore)
}
return nil
}
// insertSampleUsers will create 2 admin users for a seed database
func insertSampleUsers() error {
u2 := &types.User{
Username: "testadmin",
Password: "password123",
Email: "info@betatude.com",
Admin: types.NewNullBool(true),
}
if _, err := database.Create(u2); err != nil {
return types.ErrWrap(err, types.ErrorCreateUser)
}
u3 := &types.User{
Username: "testadmin2",
Password: "password123",
Email: "info@adminhere.com",
Admin: types.NewNullBool(true),
}
if _, err := database.Create(u3); err != nil {
return types.ErrWrap(err, types.ErrorCreateUser)
}
return nil
}
func insertMessages() error {
m1 := &types.Message{
Title: "Routine Downtime",
Description: "This is an example a upcoming message for a service!",
ServiceId: 1,
StartOn: time.Now().UTC().Add(15 * time.Minute),
EndOn: time.Now().UTC().Add(2 * time.Hour),
}
if _, err := database.Create(m1); err != nil {
return types.ErrWrap(err, types.ErrorCreateMessage)
}
m2 := &types.Message{
Title: "Server Reboot",
Description: "This is another example a upcoming message for a service!",
ServiceId: 3,
StartOn: time.Now().UTC().Add(15 * time.Minute),
EndOn: time.Now().UTC().Add(2 * time.Hour),
}
if _, err := database.Create(m2); err != nil {
return types.ErrWrap(err, types.ErrorCreateMessage)
}
return nil
}
// InsertLargeSampleData will create the example/dummy services for testing the Statping server
func InsertLargeSampleData() error {
if err := insertSampleCore(); err != nil {
return err
}
if err := InsertSampleData(); err != nil {
return err
}
if err := insertSampleUsers(); err != nil {
return err
}
if err := insertSampleCheckins(); err != nil {
return err
}
if err := insertMessages(); err != nil {
return err
}
createdOn := time.Now().UTC().Add((-24 * 90) * time.Hour)
s6 := &types.Service{
Name: "JSON Lint",
Domain: "https://jsonlint.com",
ExpectedStatus: 200,
Interval: 15,
Type: "http",
Method: "GET",
Timeout: 10,
Order: 6,
CreatedAt: createdOn,
}
if _, err := database.Create(s6); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s7 := &types.Service{
Name: "Demo Page",
Domain: "https://demo.statping.com",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 15,
Order: 7,
CreatedAt: createdOn,
}
if _, err := database.Create(s7); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s8 := &types.Service{
Name: "Golang",
Domain: "https://golang.org",
ExpectedStatus: 200,
Interval: 15,
Type: "http",
Method: "GET",
Timeout: 10,
Order: 8,
}
if _, err := database.Create(s8); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s9 := &types.Service{
Name: "Santa Monica",
Domain: "https://www.santamonica.com",
ExpectedStatus: 200,
Interval: 15,
Type: "http",
Method: "GET",
Timeout: 10,
Order: 9,
CreatedAt: createdOn,
}
if _, err := database.Create(s9); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s10 := &types.Service{
Name: "Oeschs Die Dritten",
Domain: "https://www.oeschs-die-dritten.ch/en/",
ExpectedStatus: 200,
Interval: 15,
Type: "http",
Method: "GET",
Timeout: 10,
Order: 10,
CreatedAt: createdOn,
}
if _, err := database.Create(s10); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s11 := &types.Service{
Name: "XS Project - Bochka, Bass, Kolbaser",
Domain: "https://www.youtube.com/watch?v=VLW1ieY4Izw",
ExpectedStatus: 200,
Interval: 60,
Type: "http",
Method: "GET",
Timeout: 20,
Order: 11,
CreatedAt: createdOn,
}
if _, err := database.Create(s11); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s12 := &types.Service{
Name: "Github",
Domain: "https://github.com/hunterlong",
ExpectedStatus: 200,
Interval: 60,
Type: "http",
Method: "GET",
Timeout: 20,
Order: 12,
CreatedAt: createdOn,
}
if _, err := database.Create(s12); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s13 := &types.Service{
Name: "Failing URL",
Domain: "http://thisdomainisfakeanditsgoingtofail.com",
ExpectedStatus: 200,
Interval: 45,
Type: "http",
Method: "GET",
Timeout: 10,
Order: 13,
CreatedAt: createdOn,
}
if _, err := database.Create(s13); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s14 := &types.Service{
Name: "Oesch's die Dritten - Die Jodelsprache",
Domain: "https://www.youtube.com/watch?v=k3GTxRt4iao",
ExpectedStatus: 200,
Interval: 60,
Type: "http",
Method: "GET",
Timeout: 12,
Order: 14,
CreatedAt: createdOn,
}
if _, err := database.Create(s14); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
s15 := &types.Service{
Name: "Gorm",
Domain: "http://gorm.io/",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 12,
Order: 15,
CreatedAt: createdOn,
}
if _, err := database.Create(s15); err != nil {
return types.ErrWrap(err, types.ErrorCreateService)
}
var dayAgo = time.Now().UTC().Add((-24 * 90) * time.Hour)
CoreApp.services, _ = SelectAllServices(false)
insertHitRecords(dayAgo, 5450)
insertFailureRecords(dayAgo, 730)
return nil
}
// insertFailureRecords will create failures for 15 services from seed
func insertFailureRecords(since time.Time, amount int) {
for i := int64(14); i <= 15; i++ {
service := SelectService(i)
log.Infoln(fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
createdAt := since
for fi := 1; fi <= amount; fi++ {
createdAt = createdAt.Add(2 * time.Minute)
failure := &types.Failure{
Service: service.Id,
Issue: "testing right here",
CreatedAt: createdAt,
}
database.Create(failure)
}
}
}
// insertHitRecords will create successful Hit records for 15 services
func insertHitRecords(since time.Time, amount int) error {
for i := int64(1); i <= 15; i++ {
service := SelectService(i)
log.Infoln(fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
createdAt := since
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano())
for hi := 1; hi <= amount; hi++ {
latency := p.Noise1D(float64(hi / 10))
createdAt = createdAt.Add(1 * time.Minute)
hit := &types.Hit{
Service: service.Id,
CreatedAt: createdAt.UTC(),
Latency: latency,
}
if _, err := database.Create(hit); err != nil {
return types.ErrWrap(err, types.ErrorCreateHit, service.Id)
}
}
}
return nil
}
// TmpRecords is used for testing Statping. It will create a SQLite database file
// with sample data and store it in the /tmp folder to be used by the tests.
func TmpRecords(dbFile string) error {
var sqlFile = utils.Directory + "/" + dbFile
if err := utils.CreateDirectory(utils.Directory + "/tmp"); err != nil {
log.Error(err)
}
var tmpSqlFile = utils.Directory + "/tmp/" + types.SqliteFilename
SampleHits = 480
var err error
CoreApp = NewCore()
CoreApp.Name = "Tester"
CoreApp.Setup = true
configs := &types.DbConfig{
DbConn: "sqlite",
Project: "Tester",
Location: utils.Directory,
SqlFile: sqlFile,
}
log.Infoln("saving config.yml in: " + utils.Directory)
if err := SaveConfig(configs); err != nil {
log.Error(err)
}
log.Infoln("loading config.yml from: " + utils.Directory)
if configs, err = LoadConfigFile(utils.Directory); err != nil {
log.Error(err)
}
log.Infoln("connecting to database")
exists := utils.FileExists(tmpSqlFile)
if exists {
log.Infoln(tmpSqlFile + " was found, copying the temp database to " + sqlFile)
if err := utils.DeleteFile(sqlFile); err != nil {
log.Error(err)
}
if err := utils.CopyFile(tmpSqlFile, sqlFile); err != nil {
log.Error(err)
}
log.Infoln("loading config.yml from: " + utils.Directory)
if err := CoreApp.Connect(configs, false, utils.Directory); err != nil {
log.Error(err)
}
log.Infoln("selecting the Core variable")
if _, err := SelectCore(); err != nil {
log.Error(err)
}
log.Infoln("inserting notifiers into database")
if err := InsertNotifierDB(); err != nil {
log.Error(err)
}
log.Infoln("inserting integrations into database")
if err := InsertIntegratorDB(); err != nil {
log.Error(err)
}
log.Infoln("loading all services")
if _, err := SelectAllServices(false); err != nil {
return err
}
if err := AttachNotifiers(); err != nil {
log.Error(err)
}
if err := AddIntegrations(); err != nil {
log.Error(err)
}
CoreApp.Notifications = notifier.AllCommunications
return nil
}
log.Infoln(tmpSqlFile + " not found, creating a new database...")
if err := CoreApp.Connect(configs, false, utils.Directory); err != nil {
return err
}
log.Infoln("creating database")
if err := CoreApp.CreateDatabase(); err != nil {
return err
}
log.Infoln("migrating database")
if err := CoreApp.MigrateDatabase(); err != nil {
return err
}
log.Infoln("insert large sample data into database")
if err := InsertLargeSampleData(); err != nil {
return err
}
log.Infoln("selecting the Core variable")
if CoreApp, err = SelectCore(); err != nil {
return err
}
log.Infoln("inserting notifiers into database")
if err := InsertNotifierDB(); err != nil {
return err
}
log.Infoln("inserting integrations into database")
if err := InsertIntegratorDB(); err != nil {
return err
}
log.Infoln("loading all services")
if _, err := SelectAllServices(false); err != nil {
return err
}
log.Infoln("copying sql database file to: " + tmpSqlFile)
if err := utils.CopyFile(sqlFile, tmpSqlFile); err != nil {
return err
}
return err
}

View File

@ -1,163 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"sort"
"time"
)
type Service struct {
*database.ServiceObj
}
func Services() map[int64]*Service {
return CoreApp.services
}
// SelectService returns a *core.Service from in memory
func SelectService(id int64) *Service {
service := CoreApp.services[id]
if service != nil {
return service
}
return nil
}
func (s *Service) AfterCreate(obj interface{}, err error) {
}
// CheckinProcess runs the checkin routine for each checkin attached to service
func CheckinProcess(s database.Servicer) {
for _, c := range s.Checkins() {
c.Start()
go CheckinRoutine(c)
}
}
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services
// should only be called once on startup.
func SelectAllServices(start bool) (map[int64]*Service, error) {
services := make(map[int64]*Service)
if len(CoreApp.services) > 0 {
return CoreApp.services, nil
}
for _, s := range database.Services() {
if start {
s.Start()
CheckinProcess(s)
}
fails := s.Failures().Last(limitedFailures)
s.Service.Failures = fails
for _, c := range s.Checkins() {
s.Service.Checkins = append(s.Service.Checkins, c.Checkin)
}
// collect initial service stats
s.UpdateStats()
services[s.Id] = &Service{s}
}
CoreApp.services = services
reorderServices()
return services, nil
}
func wrapFailures(f []*types.Failure) []*Failure {
var fails []*Failure
for _, v := range f {
fails = append(fails, &Failure{v})
}
return fails
}
// reorderServices will sort the services based on 'order_id'
func reorderServices() {
sort.Sort(ServiceOrder(CoreApp.services))
}
// updateService will update a service in the []*core.Services slice
func updateService(s *Service) {
CoreApp.services[s.Id] = s
}
// Delete will remove a service from the database, it will also end the service checking go routine
func (s *Service) Delete() error {
err := database.Delete(s)
if err != nil {
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err))
return err
}
s.Close()
CoreApp.services[s.Id] = nil
reorderServices()
notifier.OnDeletedService(s.Service)
return err
}
// Update will update a service in the database, the service's checking routine can be restarted by passing true
func Update(s *Service, restart bool) error {
err := database.Update(s)
if err != nil {
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
return err
}
// clear the notification queue for a service
if !s.AllowNotifications.Bool {
for _, n := range CoreApp.Notifications {
notif := n.(notifier.Notifier).Select()
notif.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
}
}
if restart {
s.Close()
s.Start()
s.SleepDuration = s.Duration()
go ServiceCheckQueue(s, true)
}
reorderServices()
updateService(s)
notifier.OnUpdatedService(s.Service)
return err
}
// Create will create a service and insert it into the database
func Create(srv database.Servicer, check bool) (int64, error) {
s := srv.Model()
s.CreatedAt = time.Now().UTC()
_, err := database.Create(s)
if err != nil {
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, err))
return 0, err
}
service := &Service{s}
s.Start()
CoreApp.services[service.Id] = service
go ServiceCheckQueue(service, check)
reorderServices()
notifier.OnNewService(s.Service)
return s.Id, nil
}

View File

@ -1 +0,0 @@
package core

View File

@ -1,115 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"golang.org/x/crypto/bcrypt"
"time"
)
type User struct {
*database.UserObj
}
// ReturnUser returns *core.User based off a *types.User
func uwrap(u *database.UserObj) *User {
return &User{u}
}
// SelectUser returns the User based on the User's ID.
func SelectUser(id int64) (*User, error) {
user, err := database.User(id)
if err != nil {
return nil, err
}
return uwrap(user), err
}
// SelectUsername returns the User based on the User's username
func SelectUsername(username string) (*User, error) {
user, err := database.UserByUsername(username)
if err != nil {
return nil, err
}
return uwrap(user), err
}
// Delete will remove the User record from the database
func (u *User) Delete() error {
return database.Delete(u)
}
// Update will update the User's record in database
func (u *User) Update() error {
u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10)
return database.Update(u)
}
// Create will insert a new User into the database
func (u *User) Create() (int64, error) {
u.CreatedAt = time.Now().UTC()
u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10)
user, err := database.Create(u)
if err != nil {
return 0, err
}
if user.Id == 0 {
log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, err))
return 0, err
}
return u.Id, err
}
// SelectAllUsers returns all users
func SelectAllUsers() []*types.User {
users := database.AllUsers()
return users
}
// AuthUser will return the User and a boolean if authentication was correct.
// AuthUser accepts username, and password as a string
func AuthUser(username, password string) (*types.User, bool) {
user, err := database.UserByUsername(username)
if err != nil {
log.Warnln(fmt.Errorf("user %v not found", username))
return nil, false
}
fmt.Println(username, password)
fmt.Println(username, user.Password)
if CheckHash(password, user.Password) {
user.UpdatedAt = time.Now().UTC()
database.Update(user)
return user.User, true
}
return nil, false
}
// CheckHash returns true if the password matches with a hashed bcrypt password
func CheckHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

View File

@ -1,8 +0,0 @@
package database
import "github.com/hunterlong/statping/types"
type CheckinHitObj struct {
hits []*types.CheckinHit
o *Object
}

View File

@ -1,131 +0,0 @@
package database
import (
"fmt"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"time"
)
type CheckinObj struct {
*types.Checkin
o *Object
Checkiner
}
type Checkiner interface {
Hits() *CheckinHitObj
Failures() *FailureObj
Model() *types.Checkin
Service() *ServiceObj
}
func (c *CheckinObj) BeforeCreate() (err error) {
c.ApiKey = utils.RandomString(7)
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now().UTC()
c.UpdatedAt = time.Now().UTC()
}
return
}
func (c *CheckinObj) BeforeDelete(tx Database) (err error) {
q := tx.Services().Where("id = ?", c.ServiceId).
Update("group_id", 0)
return q.Error()
}
func Checkin(id int64) (*CheckinObj, error) {
var checkin types.Checkin
query := database.Checkins().Where("id = ?", id)
finder := query.Find(&checkin)
return &CheckinObj{Checkin: &checkin, o: wrapObject(id, &checkin, query)}, finder.Error()
}
func CheckinByKey(api string) (*CheckinObj, error) {
var checkin types.Checkin
query := database.Checkins().Where("api = ?", api)
finder := query.Find(&checkin)
return &CheckinObj{Checkin: &checkin, o: wrapObject(checkin.Id, &checkin, query)}, finder.Error()
}
func wrapCheckins(all []*types.Checkin, db Database) []*CheckinObj {
var arr []*CheckinObj
for _, v := range all {
arr = append(arr, &CheckinObj{Checkin: v, o: wrapObject(v.Id, v, db)})
}
return arr
}
func AllCheckins() []*CheckinObj {
var checkins []*types.Checkin
query := database.Checkins()
query.Find(&checkins)
return wrapCheckins(checkins, query)
}
func (s *CheckinObj) Service() *ServiceObj {
var srv *types.Service
q := database.Services().Where("id = ?", s.ServiceId)
q.Find(&srv)
return &ServiceObj{
Service: srv,
o: wrapObject(s.ServiceId, srv, q),
}
}
func (s *CheckinObj) Failures() *FailureObj {
q := database.Failures().
Where("method = 'checkin' AND id = ?", s.Id).
Where("method = 'checkin'")
return &FailureObj{wrapObject(s.Id, nil, q)}
}
func (s *CheckinObj) object() *Object {
return s.o
}
func (c *CheckinObj) Model() *types.Checkin {
return c.Checkin
}
// Period will return the duration of the Checkin interval
func (c *CheckinObj) Period() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.Interval))
return duration
}
// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response)
func (c *CheckinObj) Grace() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod))
return duration
}
// Expected returns the duration of when the serviec should receive a Checkin
func (c *CheckinObj) Expected() time.Duration {
last := c.Hits().Last()
now := time.Now().UTC()
lastDir := now.Sub(last.CreatedAt)
sub := time.Duration(c.Period() - lastDir)
return sub
}
// Last returns the last checkinHit for a Checkin
func (c *CheckinObj) Hits() *CheckinHitObj {
var checkinHits []*types.CheckinHit
query := database.CheckinHits().Where("checkin = ?", c.Id)
query.Find(&checkinHits)
return &CheckinHitObj{checkinHits, wrapObject(c.Id, checkinHits, query)}
}
// Last returns the last checkinHit for a Checkin
func (c *CheckinHitObj) Last() *types.CheckinHit {
var last types.CheckinHit
c.o.db.Last(&last)
return &last
}
func (c *CheckinObj) Link() string {
return fmt.Sprintf("%v/checkin/%v", "DOMAINHERE", c.ApiKey)
}

View File

@ -1,80 +0,0 @@
package database
import (
"github.com/hunterlong/statping/types"
"reflect"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type CrudObject interface {
Create()
}
type Object struct {
Id int64
model interface{}
db Database
}
type isObject interface {
object() *Object
}
func wrapObject(id int64, model interface{}, db Database) *Object {
return &Object{
Id: id,
model: model,
db: db,
}
}
func modelId(model interface{}) int64 {
switch model.(type) {
case *types.Core:
return 0
default:
iface := reflect.ValueOf(model)
field := iface.Elem().FieldByName("Id")
return field.Int()
}
}
type CreateCallback func(interface{}, error)
func runCallbacks(data interface{}, err error, fns ...AfterCreate) {
for _, fn := range fns {
fn.AfterCreate(data, err)
}
}
type AfterCreate interface {
AfterCreate(interface{}, error)
}
func Create(data interface{}, fns ...AfterCreate) (*Object, error) {
model := database.Model(&data)
if err := model.Create(data).Error(); err != nil {
runCallbacks(data, err, fns...)
return nil, err
}
obj := &Object{
Id: modelId(data),
model: data,
db: model,
}
runCallbacks(data, nil, fns...)
return obj, nil
}
func Update(data interface{}) error {
model := database.Model(&data)
return model.Update(&data).Error()
}
func Delete(data interface{}) error {
model := database.Model(&data)
return model.Delete(data).Error()
}

View File

@ -2,9 +2,7 @@ package database
import (
"database/sql"
"github.com/hunterlong/statping/types"
"github.com/jinzhu/gorm"
"net/http"
"strings"
"time"
@ -111,25 +109,10 @@ type Database interface {
FormatTime(t time.Time) string
ParseTime(t string) (time.Time, error)
Requests(*http.Request, isObject) Database
Objects
}
type Objects interface {
Core() Database
Services() Database
Users() Database
Groups() Database
Incidents() Database
IncidentUpdates() Database
Hits() Database
Failures() Database
Checkins() Database
CheckinHits() Database
Messages() Database
Integrations() Database
func DB() Database {
return database
}
func Close() error {
@ -152,17 +135,6 @@ func Begin(model interface{}) Database {
return database.Model(model).Begin()
}
func Core() Database {
return database.Core()
}
func Get() Database {
if Available() {
return database
}
return nil
}
func Available() bool {
if database == nil {
return false
@ -173,59 +145,6 @@ func Available() bool {
return true
}
func (d *Db) Core() Database {
return d.Table("core").Model(&types.Service{})
}
func (d *Db) Services() Database {
return d.Model(&types.Service{})
}
func (d *Db) Users() Database {
return d.Model(&types.User{})
}
func (d *Db) Groups() Database {
return d.Model(&types.Group{})
}
func (d *Db) Incidents() Database {
return d.Model(&types.Incident{})
}
func (d *Db) IncidentUpdates() Database {
return d.Model(&types.IncidentUpdate{})
}
func (d *Db) Hits() Database {
return d.Model(&types.Hit{})
}
func (d *Db) Integrations() Database {
return d.Model(&types.Integration{})
}
func (d *Db) Failures() Database {
return d.Model(&types.Failure{})
}
func (d *Db) Checkins() Database {
return d.Model(&types.Checkin{})
}
func (d *Db) CheckinHits() Database {
return d.Model(&types.CheckinHit{})
}
func (d *Db) Messages() Database {
return d.Model(&types.Message{})
}
func (it *Db) Requests(r *http.Request, o isObject) Database {
g := ParseQueries(r, o)
return g.db
}
func (it *Db) MultipleSelects(args ...string) Database {
joined := strings.Join(args, ", ")
return it.Select(joined)
@ -245,7 +164,8 @@ func Openw(dialect string, args ...interface{}) (db Database, err error) {
if err != nil {
return nil, err
}
database = Wrap(gormdb)
db = Wrap(gormdb)
database = db
return database, err
}

44
database/database_test.go Normal file
View File

@ -0,0 +1,44 @@
package database
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestDbConnection(t *testing.T) {
err := CoreApp.Connect(configs, false, dir)
assert.Nil(t, err)
}
func TestDropDatabase(t *testing.T) {
if skipNewDb {
t.SkipNow()
}
err := CoreApp.DropDatabase()
assert.Nil(t, err)
}
func TestSeedSchemaDatabase(t *testing.T) {
if skipNewDb {
t.SkipNow()
}
err := CoreApp.CreateDatabase()
assert.Nil(t, err)
}
func TestMigrateDatabase(t *testing.T) {
t.SkipNow()
err := CoreApp.MigrateDatabase()
assert.Nil(t, err)
}
func TestSeedDatabase(t *testing.T) {
err := InsertLargeSampleData()
assert.Nil(t, err)
}
func TestReLoadDbConfig(t *testing.T) {
err := CoreApp.Connect(configs, false, dir)
assert.Nil(t, err)
assert.Equal(t, "sqlite", CoreApp.config.DbConn)
}

View File

@ -1,57 +0,0 @@
package database
import (
"github.com/hunterlong/statping/types"
"time"
)
type FailureObj struct {
o *Object
}
type Failurer interface {
Model() []*types.Failure
}
func (f *FailureObj) Model() []*types.Failure {
return f.All()
}
func (f *FailureObj) All() []*types.Failure {
var fails []*types.Failure
f.o.db.Find(&fails)
return fails
}
func AllFailures() int {
var amount int
database.Failures().Count(&amount)
return amount
}
func (f *FailureObj) DeleteAll() error {
query := database.Exec(`DELETE FROM failures WHERE service = ?`, f.o.Id)
return query.Error()
}
func (f *FailureObj) Last(amount int) []*types.Failure {
var fail []*types.Failure
f.o.db.Limit(amount).Find(&fail)
return fail
}
func (f *FailureObj) Count() int {
var amount int
f.o.db.Count(&amount)
return amount
}
func (f *FailureObj) Since(t time.Time) []*types.Failure {
var fails []*types.Failure
f.o.db.Since(t).Find(&fails)
return fails
}
func (f *FailureObj) object() *Object {
return f.o
}

View File

@ -1,47 +0,0 @@
package database
import "github.com/hunterlong/statping/types"
type GroupObj struct {
*types.Group
o *Object
Grouper
}
type Grouper interface {
Services() []*types.Service
Model() *types.Group
}
func AllGroups() []*GroupObj {
var groups []*types.Group
query := database.Groups()
query.Find(&groups)
return wrapGroups(groups, query)
}
func (g *Db) GetGroup(id int64) (*GroupObj, error) {
var group types.Group
query := database.Groups().Where("id = ?", id)
finder := query.Find(&group)
return &GroupObj{Group: &group, o: wrapObject(id, &group, query)}, finder.Error()
}
func (g *GroupObj) Services() []*types.Service {
var services []*types.Service
database.Services().Where("group = ?", g.Id).Find(&services)
return services
}
func (g *GroupObj) Model() *types.Group {
return g.Group
}
func wrapGroups(all []*types.Group, db Database) []*GroupObj {
var arr []*GroupObj
for _, v := range all {
arr = append(arr, &GroupObj{Group: v, o: wrapObject(v.Id, v, db)})
}
return arr
}

View File

@ -38,7 +38,7 @@ type GroupQuery struct {
}
func (b GroupQuery) Find(data interface{}) error {
return b.db.Find(&data).Error()
return b.db.Find(data).Error()
}
func (b GroupQuery) Database() Database {
@ -176,6 +176,10 @@ func (g *GroupQuery) duration() time.Duration {
}
}
type isObject interface {
Db() Database
}
func ParseQueries(r *http.Request, o isObject) *GroupQuery {
fields := parseGet(r)
grouping := fields.Get("group")
@ -192,7 +196,7 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
limit = 10000
}
db := o.object().db
db := o.Db()
query := &GroupQuery{
Start: time.Unix(startField, 0).UTC(),
@ -205,22 +209,22 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
db: db,
}
if startField == 0 {
query.Start = time.Now().Add(-3 * types.Month).UTC()
}
if endField == 0 {
query.End = time.Now().UTC()
}
if query.Limit != 0 {
db = db.Limit(query.Limit)
}
if query.Offset > 0 {
db = db.Offset(query.Offset)
}
if !query.Start.IsZero() && !query.End.IsZero() {
db = db.Where("created_at BETWEEN ? AND ?", db.FormatTime(query.Start), db.FormatTime(query.End))
} else {
if !query.Start.IsZero() {
db = db.Where("created_at > ?", db.FormatTime(query.Start))
}
if !query.End.IsZero() {
db = db.Where("created_at < ?", db.FormatTime(query.End))
}
}
db = db.Where("created_at BETWEEN ? AND ?", db.FormatTime(query.Start), db.FormatTime(query.End))
if query.Order != "" {
db = db.Order(query.Order)
}

View File

@ -1,53 +1 @@
package database
import (
"github.com/hunterlong/statping/types"
"time"
)
type HitObj struct {
o *Object
}
func (h *HitObj) All() []*types.Hit {
var fails []*types.Hit
h.o.db.Find(&fails)
return fails
}
func (s *ServiceObj) CreateHit(hit *types.Hit) *HitObj {
hit.Service = s.Id
database.Create(hit)
return &HitObj{wrapObject(hit.Id, hit, database.Hits().Where("id = ?", hit.Id))}
}
func (h *HitObj) Sum() float64 {
result := struct {
amount float64
}{0}
h.o.db.Select("AVG(latency) as amount").Scan(&result).Debug()
return result.amount
}
func (h *HitObj) Last(amount int) []*types.Hit {
var hits []*types.Hit
h.o.db.Limit(amount).Find(&hits)
return hits
}
func (h *HitObj) Since(t time.Time) []*types.Hit {
var hits []*types.Hit
h.o.db.Since(t).Find(&hits)
return hits
}
func (h *HitObj) Count() int {
var amount int
h.o.db.Count(&amount)
return amount
}
func (h *HitObj) object() *Object {
return h.o
}

View File

@ -1,31 +1 @@
package database
import "github.com/hunterlong/statping/types"
type IncidentObj struct {
*types.Incident
o *Object
}
func Incident(id int64) (*IncidentObj, error) {
var incident types.Incident
query := database.Incidents().Where("id = ?", id)
finder := query.Find(&incident)
return &IncidentObj{Incident: &incident, o: wrapObject(id, &incident, query)}, finder.Error()
}
func AllIncidents() []*types.Incident {
var incidents []*types.Incident
database.Incidents().Find(&incidents)
return incidents
}
func (i *IncidentObj) Updates() []*types.IncidentUpdate {
var incidents []*types.IncidentUpdate
database.IncidentUpdates().Where("incident = ?", i.Id).Find(&incidents)
return incidents
}
func (i *IncidentObj) object() *Object {
return i.o
}

View File

@ -1,19 +1 @@
package database
import "github.com/hunterlong/statping/types"
type IntegrationObj struct {
*types.Integration
o *Object
}
func Integration(id int64) (*IntegrationObj, error) {
var integration types.Integration
query := database.Model(&types.Integration{}).Where("id = ?", id)
finder := query.Find(&integration)
return &IntegrationObj{Integration: &integration, o: wrapObject(id, &integration, query)}, finder.Error()
}
func (i *IntegrationObj) object() *Object {
return i.o
}

27
database/interface.go Normal file
View File

@ -0,0 +1,27 @@
package database
type DbObject interface {
Create() error
Update() error
Delete() error
}
type Sampler interface {
Sample() DbObject
}
func MigrateTable(table interface{}) error {
tx := database.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
tx = tx.AutoMigrate(table)
if err := tx.Commit().Error(); err != nil {
return err
}
return nil
}

View File

@ -1,25 +1 @@
package database
import "github.com/hunterlong/statping/types"
type MessageObj struct {
*types.Message
o *Object
}
func Message(id int64) (*MessageObj, error) {
var message types.Message
query := database.Messages().Where("id = ?", id)
finder := query.Find(&message)
return &MessageObj{Message: &message, o: wrapObject(id, &message, query)}, finder.Error()
}
func AllMessages() []*types.Message {
var messages []*types.Message
database.Messages().Find(&messages)
return messages
}
func (m *MessageObj) object() *Object {
return m.o
}

173
database/sample.go Normal file
View File

@ -0,0 +1,173 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package database
import (
"time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
var (
sampleStart = time.Now().Add((-24 * 7) * time.Hour).UTC()
SampleHits = 9900.
)
// InsertSampleHits will create a couple new hits for the sample services
func InsertSampleHits() error {
//tx := Begin(&hits.Hit{})
//sg := new(sync.WaitGroup)
//for i := int64(1); i <= 5; i++ {
// sg.Add(1)
// service := SelectService(i)
// seed := time.Now().UnixNano()
// log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
// createdAt := sampleStart
// p := utils.NewPerlin(2., 2., 10, seed)
// go func(sg *sync.WaitGroup) {
// defer sg.Done()
// for hi := 0.; hi <= float64(SampleHits); hi++ {
// latency := p.Noise1D(hi / 500)
// createdAt = createdAt.Add(60 * time.Second)
// hit := &hits.Hit{
// Service: service.Id,
// CreatedAt: createdAt,
// Latency: latency,
// }
// tx = tx.Create(&hit)
// }
// }(sg)
//}
//sg.Wait()
//if err := tx.Commit().Error(); err != nil {
// log.Errorln(err)
// return types.ErrWrap(err, types.ErrorCreateSampleHits)
//}
return nil
}
// TmpRecords is used for testing Statping. It will create a SQLite database file
// with sample data and store it in the /tmp folder to be used by the tests.
//func TmpRecords(dbFile string) error {
// var sqlFile = utils.Directory + "/" + dbFile
// if err := utils.CreateDirectory(utils.Directory + "/tmp"); err != nil {
// log.Error(err)
// }
// var tmpSqlFile = utils.Directory + "/tmp/" + types.SqliteFilename
// SampleHits = 480
//
// var err error
// CoreApp = NewCore()
// CoreApp.Name = "Tester"
// CoreApp.Setup = true
// configs := &types.DbConfig{
// DbConn: "sqlite",
// Project: "Tester",
// Location: utils.Directory,
// SqlFile: sqlFile,
// }
// log.Infoln("saving config.yml in: " + utils.Directory)
// if err := configs.Save(utils.Directory); err != nil {
// log.Error(err)
// }
//
// log.Infoln("loading config.yml from: " + utils.Directory)
// if configs, err = LoadConfigs(); err != nil {
// log.Error(err)
// }
// log.Infoln("connecting to database")
//
// exists := utils.FileExists(tmpSqlFile)
// if exists {
// log.Infoln(tmpSqlFile + " was found, copying the temp database to " + sqlFile)
// if err := utils.DeleteFile(sqlFile); err != nil {
// log.Error(err)
// }
// if err := utils.CopyFile(tmpSqlFile, sqlFile); err != nil {
// log.Error(err)
// }
// log.Infoln("loading config.yml from: " + utils.Directory)
//
// if err := CoreApp.Connect(configs, false, utils.Directory); err != nil {
// log.Error(err)
// }
// log.Infoln("selecting the Core variable")
// if _, err := SelectCore(); err != nil {
// log.Error(err)
// }
// log.Infoln("inserting notifiers into database")
// if err := InsertNotifierDB(); err != nil {
// log.Error(err)
// }
// log.Infoln("inserting integrations into database")
// if err := InsertIntegratorDB(); err != nil {
// log.Error(err)
// }
// log.Infoln("loading all services")
// if _, err := SelectAllServices(false); err != nil {
// return err
// }
// if err := AttachNotifiers(); err != nil {
// log.Error(err)
// }
// if err := AddIntegrations(); err != nil {
// log.Error(err)
// }
// CoreApp.Notifications = notifier.AllCommunications
// return nil
// }
//
// log.Infoln(tmpSqlFile + " not found, creating a new database...")
//
// if err := CoreApp.Connect(configs, false, utils.Directory); err != nil {
// return err
// }
// log.Infoln("creating database")
// if err := CoreApp.CreateDatabase(); err != nil {
// return err
// }
// log.Infoln("migrating database")
// if err := MigrateDatabase(); err != nil {
// return err
// }
// log.Infoln("insert large sample data into database")
// if err := InsertLargeSampleData(); err != nil {
// return err
// }
// log.Infoln("selecting the Core variable")
// if CoreApp, err = SelectCore(); err != nil {
// return err
// }
// log.Infoln("inserting notifiers into database")
// if err := InsertNotifierDB(); err != nil {
// return err
// }
// log.Infoln("inserting integrations into database")
// if err := InsertIntegratorDB(); err != nil {
// return err
// }
// log.Infoln("loading all services")
// if _, err := SelectAllServices(false); err != nil {
// return err
// }
// log.Infoln("copying sql database file to: " + tmpSqlFile)
// if err := utils.CopyFile(sqlFile, tmpSqlFile); err != nil {
// return err
// }
// return err
//}

View File

@ -1,213 +1 @@
package database
import (
"fmt"
"github.com/hunterlong/statping/types"
"strconv"
"strings"
"time"
)
type ServiceObj struct {
*types.Service
o *Object
Servicer
}
type Servicer interface {
Failures() *FailureObj
Checkins() []*CheckinObj
DowntimeText() string
UpdateStats()
Model() *ServiceObj
Hittable
}
type Hittable interface {
Hits() *HitObj
CreateHit(hit *types.Hit) *HitObj
}
func (s *ServiceObj) Model() *ServiceObj {
return s
}
func Service(id int64) (*ServiceObj, error) {
var service types.Service
query := database.Services().Where("id = ?", id)
finer := query.Find(&service)
return &ServiceObj{Service: &service, o: wrapObject(id, &service, query)}, finer.Error()
}
func wrapServices(all []*types.Service, db Database) []*ServiceObj {
var arr []*ServiceObj
for _, v := range all {
arr = append(arr, &ServiceObj{Service: v, o: wrapObject(v.Id, v, db)})
}
return arr
}
func Services() []*ServiceObj {
var services []*types.Service
db := database.Services().Order("order_id desc")
db.Find(&services)
return wrapServices(services, db)
}
func (s *ServiceObj) Checkins() []*CheckinObj {
var checkins []*types.Checkin
query := database.Checkins().Where("service = ?", s.Id)
query.Find(&checkins)
return wrapCheckins(checkins, query)
}
func (s *ServiceObj) DowntimeText() string {
last := s.Failures().Last(1)
if len(last) == 0 {
return ""
}
return parseError(last[0])
}
// ParseError returns a human readable error for a Failure
func parseError(f *types.Failure) string {
if f.Method == "checkin" {
return fmt.Sprintf("Checkin is Offline")
}
err := strings.Contains(f.Issue, "connection reset by peer")
if err {
return fmt.Sprintf("Connection Reset")
}
err = strings.Contains(f.Issue, "operation timed out")
if err {
return fmt.Sprintf("HTTP Request Timed Out")
}
err = strings.Contains(f.Issue, "x509: certificate is valid")
if err {
return fmt.Sprintf("SSL Certificate invalid")
}
err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers")
if err {
return fmt.Sprintf("Connection Timed Out")
}
err = strings.Contains(f.Issue, "no such host")
if err {
return fmt.Sprintf("Domain is offline or not found")
}
err = strings.Contains(f.Issue, "HTTP Status Code")
if err {
return fmt.Sprintf("Incorrect HTTP Status Code")
}
err = strings.Contains(f.Issue, "connection refused")
if err {
return fmt.Sprintf("Connection Failed")
}
err = strings.Contains(f.Issue, "can't assign requested address")
if err {
return fmt.Sprintf("Unable to Request Address")
}
err = strings.Contains(f.Issue, "no route to host")
if err {
return fmt.Sprintf("Domain is offline or not found")
}
err = strings.Contains(f.Issue, "i/o timeout")
if err {
return fmt.Sprintf("Connection Timed Out")
}
err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body")
if err {
return fmt.Sprintf("Timed Out on Response Body")
}
return f.Issue
}
func (s *ServiceObj) Hits() *HitObj {
query := database.Hits().Where("service = ?", s.Id)
return &HitObj{wrapObject(s.Id, nil, query)}
}
func (s *ServiceObj) Failures() *FailureObj {
q := database.Failures().Where("method != 'checkin' AND service = ?", s.Id)
return &FailureObj{wrapObject(s.Id, nil, q)}
}
func (s *ServiceObj) Group() *GroupObj {
var group types.Group
q := database.Groups().Where("id = ?", s.GroupId)
finder := q.Find(&group)
if finder.Error() != nil {
return nil
}
return &GroupObj{Group: &group, o: wrapObject(group.Id, &group, q)}
}
func (s *ServiceObj) object() *Object {
return s.o
}
func (s *ServiceObj) UpdateStats() {
s.Online24Hours = s.OnlineDaysPercent(1)
s.Online7Days = s.OnlineDaysPercent(7)
s.AvgResponse = s.AvgTime()
s.FailuresLast24Hours = len(s.Failures().Since(time.Now().UTC().Add(-time.Hour * 24)))
s.Stats = &types.Stats{
Failures: s.Failures().Count(),
Hits: s.Hits().Count(),
}
}
// AvgTime will return the average amount of time for a service to response back successfully
func (s *ServiceObj) AvgTime() float64 {
sum := s.Hits().Sum()
return sum
}
// AvgUptime will return the average amount of time for a service to response back successfully
func (s *ServiceObj) AvgUptime(since time.Time) float64 {
sum := s.Hits().Sum()
return sum
}
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
func (s *ServiceObj) OnlineDaysPercent(days int) float32 {
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
return s.OnlineSince(ago)
}
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
func (s *ServiceObj) OnlineSince(ago time.Time) float32 {
failed := s.Failures().Since(ago)
if len(failed) == 0 {
s.Online24Hours = 100.00
return s.Online24Hours
}
total := s.Hits().Since(ago)
if len(total) == 0 {
s.Online24Hours = 0
return s.Online24Hours
}
avg := float64(len(failed)) / float64(len(total)) * 100
avg = 100 - avg
if avg < 0 {
avg = 0
}
amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10)
s.Online24Hours = float32(amount)
return s.Online24Hours
}
// Downtime returns the amount of time of a offline service
func (s *ServiceObj) Downtime() time.Duration {
hits := s.Hits().Last(1)
fail := s.Failures().Last(1)
if len(fail) == 0 {
return time.Duration(0)
}
if len(fail) == 0 {
return time.Now().UTC().Sub(fail[0].CreatedAt.UTC())
}
since := fail[0].CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
return since
}

15
database/setup.go Normal file
View File

@ -0,0 +1,15 @@
package database
//// SampleData runs all the sample data for a new Statping installation
//func SampleData() error {
// if err := InsertSampleData(); err != nil {
// return errors.Wrap(err, "sample data")
// }
// if err := InsertSampleHits(); err != nil {
// return errors.Wrap(err, "sample service hits")
// }
// if err := insertSampleCheckins(); err != nil {
// return errors.Wrap(err, "sample checkin examples")
// }
// return nil
//}

View File

@ -22,11 +22,11 @@ func (it *Db) ParseTime(t string) (time.Time, error) {
func (it *Db) FormatTime(t time.Time) string {
switch it.Type {
case "mysql":
return t.UTC().Format("2006-01-02 15:04:05")
return t.Format("2006-01-02 15:04:05")
case "postgres":
return t.UTC().Format("2006-01-02 15:04:05.999999999")
return t.Format("2006-01-02 15:04:05.999999999")
default:
return t.UTC().Format("2006-01-02 15:04:05")
return t.Format("2006-01-02 15:04:05")
}
}

View File

@ -1,32 +1 @@
package database
import "github.com/hunterlong/statping/types"
type UserObj struct {
*types.User
o *Object
}
func User(id int64) (*UserObj, error) {
var user types.User
query := database.Users().Where("id = ?", id)
finder := query.First(&user)
return &UserObj{User: &user, o: wrapObject(id, &user, query)}, finder.Error()
}
func UserByUsername(username string) (*UserObj, error) {
var user types.User
query := database.Users().Where("username = ?", username)
finder := query.First(&user)
return &UserObj{User: &user, o: wrapObject(user.Id, &user, query)}, finder.Error()
}
func AllUsers() []*types.User {
var users []*types.User
database.Users().Find(&users)
return users
}
func (u *UserObj) object() *Object {
return u.o
}

View File

@ -9,27 +9,27 @@
dependencies:
"@babel/highlight" "^7.8.3"
"@babel/compat-data@^7.8.4":
version "7.8.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.5.tgz#d28ce872778c23551cbb9432fc68d28495b613b9"
integrity sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==
"@babel/compat-data@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.6.tgz#7eeaa0dfa17e50c7d9c0832515eee09b56f04e35"
integrity sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q==
dependencies:
browserslist "^4.8.5"
invariant "^2.2.4"
semver "^5.5.0"
"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.4.tgz#d496799e5c12195b3602d0fddd77294e3e38e80e"
integrity sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.6.tgz#27d7df9258a45c2e686b6f18b6c659e563aa4636"
integrity sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg==
dependencies:
"@babel/code-frame" "^7.8.3"
"@babel/generator" "^7.8.4"
"@babel/generator" "^7.8.6"
"@babel/helpers" "^7.8.4"
"@babel/parser" "^7.8.4"
"@babel/template" "^7.8.3"
"@babel/traverse" "^7.8.4"
"@babel/types" "^7.8.3"
"@babel/parser" "^7.8.6"
"@babel/template" "^7.8.6"
"@babel/traverse" "^7.8.6"
"@babel/types" "^7.8.6"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.1"
@ -59,12 +59,12 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/generator@^7.2.2", "@babel/generator@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e"
integrity sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==
"@babel/generator@^7.2.2", "@babel/generator@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.6.tgz#57adf96d370c9a63c241cd719f9111468578537a"
integrity sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==
dependencies:
"@babel/types" "^7.8.3"
"@babel/types" "^7.8.6"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.5.0"
@ -93,34 +93,35 @@
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/helper-compilation-targets@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz#03d7ecd454b7ebe19a254f76617e61770aed2c88"
integrity sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==
"@babel/helper-compilation-targets@^7.8.4", "@babel/helper-compilation-targets@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.6.tgz#015b85db69e3a34240d5c2b761fc53eb9695f09c"
integrity sha512-UrJdk27hKVJSnibFcUWYLkCL0ZywTUoot8yii1lsHJcvwrypagmYKjHLMWivQPm4s6GdyygCL8fiH5EYLxhQwQ==
dependencies:
"@babel/compat-data" "^7.8.4"
"@babel/compat-data" "^7.8.6"
browserslist "^4.8.5"
invariant "^2.2.4"
levenary "^1.1.1"
semver "^5.5.0"
"@babel/helper-create-class-features-plugin@^7.3.0", "@babel/helper-create-class-features-plugin@^7.3.4", "@babel/helper-create-class-features-plugin@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.3.tgz#5b94be88c255f140fd2c10dd151e7f98f4bff397"
integrity sha512-qmp4pD7zeTxsv0JNecSBsEmG1ei2MqwJq4YQcK3ZWm/0t07QstWfvuV/vm3Qt5xNMFETn2SZqpMx2MQzbtq+KA==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0"
integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==
dependencies:
"@babel/helper-function-name" "^7.8.3"
"@babel/helper-member-expression-to-functions" "^7.8.3"
"@babel/helper-optimise-call-expression" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.6"
"@babel/helper-split-export-declaration" "^7.8.3"
"@babel/helper-create-regexp-features-plugin@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79"
integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz#7fa040c97fb8aebe1247a5c645330c32d083066b"
integrity sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.8.3"
"@babel/helper-regex" "^7.8.3"
regexpu-core "^4.6.0"
@ -179,15 +180,16 @@
"@babel/types" "^7.8.3"
"@babel/helper-module-transforms@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz#d305e35d02bee720fbc2c3c3623aa0c316c01590"
integrity sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4"
integrity sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==
dependencies:
"@babel/helper-module-imports" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.6"
"@babel/helper-simple-access" "^7.8.3"
"@babel/helper-split-export-declaration" "^7.8.3"
"@babel/template" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/template" "^7.8.6"
"@babel/types" "^7.8.6"
lodash "^4.17.13"
"@babel/helper-optimise-call-expression@^7.8.3":
@ -220,15 +222,15 @@
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/helper-replace-supers@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc"
integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==
"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8"
integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.8.3"
"@babel/helper-optimise-call-expression" "^7.8.3"
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/traverse" "^7.8.6"
"@babel/types" "^7.8.6"
"@babel/helper-simple-access@^7.8.3":
version "7.8.3"
@ -273,10 +275,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.7.5", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8"
integrity sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==
"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c"
integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==
"@babel/plugin-proposal-async-generator-functions@^7.8.3":
version "7.8.3"
@ -500,17 +502,17 @@
"@babel/helper-plugin-utils" "^7.8.3"
lodash "^4.17.13"
"@babel/plugin-transform-classes@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz#46fd7a9d2bb9ea89ce88720477979fe0d71b21b8"
integrity sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==
"@babel/plugin-transform-classes@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz#77534447a477cbe5995ae4aee3e39fbc8090c46d"
integrity sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.8.3"
"@babel/helper-define-map" "^7.8.3"
"@babel/helper-function-name" "^7.8.3"
"@babel/helper-optimise-call-expression" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.6"
"@babel/helper-split-export-declaration" "^7.8.3"
globals "^11.1.0"
@ -551,10 +553,10 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-transform-for-of@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz#6fe8eae5d6875086ee185dd0b098a8513783b47d"
integrity sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==
"@babel/plugin-transform-for-of@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz#a051bd1b402c61af97a27ff51b468321c7c2a085"
integrity sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw==
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
@ -733,12 +735,12 @@
regenerator-runtime "^0.12.0"
"@babel/preset-env@^7.8.4", "@babel/preset-env@~7.8.3":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.4.tgz#9dac6df5f423015d3d49b6e9e5fa3413e4a72c4e"
integrity sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.6.tgz#2a0773b08589ecba4995fc71b1965e4f531af40b"
integrity sha512-M5u8llV9DIVXBFB/ArIpqJuvXpO+ymxcJ6e8ZAmzeK3sQeBNOD1y+rHvHCGG4TlEmsNpIrdecsHGHT8ZCoOSJg==
dependencies:
"@babel/compat-data" "^7.8.4"
"@babel/helper-compilation-targets" "^7.8.4"
"@babel/compat-data" "^7.8.6"
"@babel/helper-compilation-targets" "^7.8.6"
"@babel/helper-module-imports" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-proposal-async-generator-functions" "^7.8.3"
@ -761,13 +763,13 @@
"@babel/plugin-transform-async-to-generator" "^7.8.3"
"@babel/plugin-transform-block-scoped-functions" "^7.8.3"
"@babel/plugin-transform-block-scoping" "^7.8.3"
"@babel/plugin-transform-classes" "^7.8.3"
"@babel/plugin-transform-classes" "^7.8.6"
"@babel/plugin-transform-computed-properties" "^7.8.3"
"@babel/plugin-transform-destructuring" "^7.8.3"
"@babel/plugin-transform-dotall-regex" "^7.8.3"
"@babel/plugin-transform-duplicate-keys" "^7.8.3"
"@babel/plugin-transform-exponentiation-operator" "^7.8.3"
"@babel/plugin-transform-for-of" "^7.8.4"
"@babel/plugin-transform-for-of" "^7.8.6"
"@babel/plugin-transform-function-name" "^7.8.3"
"@babel/plugin-transform-literals" "^7.8.3"
"@babel/plugin-transform-member-expression-literals" "^7.8.3"
@ -788,7 +790,7 @@
"@babel/plugin-transform-template-literals" "^7.8.3"
"@babel/plugin-transform-typeof-symbol" "^7.8.4"
"@babel/plugin-transform-unicode-regex" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/types" "^7.8.6"
browserslist "^4.8.5"
core-js-compat "^3.6.2"
invariant "^2.2.2"
@ -802,34 +804,34 @@
dependencies:
regenerator-runtime "^0.13.2"
"@babel/template@^7.2.2", "@babel/template@^7.7.4", "@babel/template@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==
"@babel/template@^7.2.2", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==
dependencies:
"@babel/code-frame" "^7.8.3"
"@babel/parser" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/parser" "^7.8.6"
"@babel/types" "^7.8.6"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.4.tgz#f0845822365f9d5b0e312ed3959d3f827f869e3c"
integrity sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==
"@babel/traverse@^7.0.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.4", "@babel/traverse@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff"
integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==
dependencies:
"@babel/code-frame" "^7.8.3"
"@babel/generator" "^7.8.4"
"@babel/generator" "^7.8.6"
"@babel/helper-function-name" "^7.8.3"
"@babel/helper-split-export-declaration" "^7.8.3"
"@babel/parser" "^7.8.4"
"@babel/types" "^7.8.3"
"@babel/parser" "^7.8.6"
"@babel/types" "^7.8.6"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==
"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.8.3", "@babel/types@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.6.tgz#629ecc33c2557fcde7126e58053127afdb3e6d01"
integrity sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.13"
@ -1097,9 +1099,9 @@
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "13.7.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd"
integrity sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw==
version "13.7.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99"
integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
@ -1143,9 +1145,9 @@
source-map "^0.6.1"
"@types/webpack@^4.4.31":
version "4.41.6"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.6.tgz#c76afbdef59159d12e3e1332dc264b75574722a2"
integrity sha512-iWRpV5Ej+8uKrgxp6jXz3v7ZTjgtuMXY+rsxQjFNU0hYCnHkpA7vtiNffgxjuxX4feFHBbz0IF76OzX2OqDYPw==
version "4.41.7"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.7.tgz#22be27dbd4362b01c3954ca9b021dbc9328d9511"
integrity sha512-OQG9viYwO0V1NaNV7d0n79V+n6mjOV30CwgFPIfTzwmk8DHbt+C4f2aBGdCYbo3yFyYD6sjXfqqOjwkl1j+ulA==
dependencies:
"@types/anymatch" "*"
"@types/node" "*"
@ -1160,9 +1162,9 @@
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
"@types/yargs@^15.0.0":
version "15.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.3.tgz#41453a0bc7ab393e995d1f5451455638edbd2baf"
integrity sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==
version "15.0.4"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299"
integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==
dependencies:
"@types/yargs-parser" "*"
@ -1183,10 +1185,10 @@
lodash.kebabcase "^4.1.1"
svg-tags "^1.0.0"
"@vue/babel-preset-app@^4.1.2", "@vue/babel-preset-app@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.2.2.tgz#c7a0a685a5eb92e1b1538f8d1fc4f5ac00dccec1"
integrity sha512-QGgL+iR+ZdNO9xcFJqYjg938bwjArgIyNOFfM0m+dNSOt7wWVrlFA2v0C6aVN1sJ+IEjdurEolBTZ7hXp6Fbsg==
"@vue/babel-preset-app@^4.1.2", "@vue/babel-preset-app@^4.2.3":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.2.3.tgz#608b2c9f7ca677e793833662fc727ff9137a9a35"
integrity sha512-Xlc8d9Ebgu9pNZMUxKZWVP2CctVZzfX3LAxjBDWAAIiVpdXX4IkQQCevDhgiANFzlmE3KXtiSgPGs57Sso2g7Q==
dependencies:
"@babel/core" "^7.8.4"
"@babel/helper-compilation-targets" "^7.8.4"
@ -1256,13 +1258,13 @@
integrity sha512-V51eS7NIsK/rv19oK0+B5Yl/VNWCJTTkjibreIXDknOLSH3MKTOJamUI1BEYo5FOXBWw+7DLmaNF3XKemQ5Y/w==
"@vue/cli-plugin-babel@^4.1.0":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.2.2.tgz#c65cad9921713b9233bab49306559c553a78ee1d"
integrity sha512-uCXDlgUp4ehHoYosr6kbyJYeQ+aQ4lR9Zn0Bf58MFbZbmjBCi8dBKzQf7ve4bo8L8CTGjWirnzgA7pStRmWx0g==
version "4.2.3"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.2.3.tgz#8633795126f4d78c517bff9a2539229c9e0c8db4"
integrity sha512-vbK6f7dN4gj+6xyhTZkvjjbz1vsTwX+ObRD0ElaaipXo2oVSBAAPPGHkLjnH8C2brDLPeLHdUCzERzx2kc2lmQ==
dependencies:
"@babel/core" "^7.8.4"
"@vue/babel-preset-app" "^4.2.2"
"@vue/cli-shared-utils" "^4.2.2"
"@vue/babel-preset-app" "^4.2.3"
"@vue/cli-shared-utils" "^4.2.3"
babel-loader "^8.0.6"
cache-loader "^4.1.0"
thread-loader "^2.1.3"
@ -1338,25 +1340,6 @@
webpack-dev-server "^3.10.2"
webpack-merge "^4.2.2"
"@vue/cli-shared-utils@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.2.2.tgz#953fec34115cb12d0820012a9d7400f8c27d6660"
integrity sha512-EK5wcxgjadqUpSzfh6Bnxd46Zx+SAaHusygqV11UZKHr4EObc/SjCpq7c7drmFkBjRqmVvrHs4jRnJJo5VgCgQ==
dependencies:
"@hapi/joi" "^15.0.1"
chalk "^2.4.2"
execa "^1.0.0"
launch-editor "^2.2.1"
lru-cache "^5.1.1"
node-ipc "^9.1.1"
open "^6.3.0"
ora "^3.4.0"
read-pkg "^5.1.1"
request "^2.87.0"
request-promise-native "^1.0.8"
semver "^6.1.0"
strip-ansi "^6.0.0"
"@vue/cli-shared-utils@^4.2.3":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.2.3.tgz#13646452cc25b0ab68a57cb52cac27983cee39a4"
@ -1613,9 +1596,9 @@ acorn-globals@^4.3.4:
acorn-walk "^6.0.1"
acorn-jsx@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
version "5.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
acorn-walk@^6.0.1, acorn-walk@^6.1.1:
version "6.2.0"
@ -1633,9 +1616,9 @@ acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.5, acorn@^6.0.7, acorn@^6.2.1:
integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==
acorn@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
version "7.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==
address@^1.1.2:
version "1.1.2"
@ -1661,9 +1644,9 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1:
version "6.11.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9"
integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==
version "6.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7"
integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
@ -1762,9 +1745,9 @@ anymatch@^3.0.3, anymatch@~3.1.1:
picomatch "^2.0.4"
apexcharts@^3.15.0:
version "3.15.6"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.15.6.tgz#1716f8badd447b06796dbcd95b5e405ec676a3bf"
integrity sha512-8mZqg7eTZGU2zvjYUUOf+sTqgfmutipHU9lNgkqzZPtwIVGwR5PwXTBNKRJSI3AeSoQ8VZGYfzTJWoUDfGAeBw==
version "3.16.0"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.16.0.tgz#7fec05f85773c2b89a7b757cfe551a9c055807ed"
integrity sha512-tD7d/VJ9G2giVIOTowi89RKu4Ptp/jJdvrmkBJiaaOROCT3Wa4qyJ6QKp4dgUmpN5z0xMFxtDPaikItW2BEucw==
dependencies:
svg.draggable.js "^2.2.2"
svg.easing.js "^2.0.0"
@ -2226,16 +2209,7 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
browserslist@^4.0.0, browserslist@^4.8.3, browserslist@^4.8.5:
version "4.8.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.7.tgz#ec8301ff415e6a42c949d0e66b405eb539c532d0"
integrity sha512-gFOnZNYBHrEyUML0xr5NJ6edFaaKbTFX9S9kQHlYfCP0Rit/boRIz4G+Avq6/4haEKJXdGGUnoolx+5MWW2BoA==
dependencies:
caniuse-lite "^1.0.30001027"
electron-to-chromium "^1.3.349"
node-releases "^1.1.49"
browserslist@^4.8.6:
browserslist@^4.0.0, browserslist@^4.8.3, browserslist@^4.8.5, browserslist@^4.8.6:
version "4.9.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c"
integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==
@ -2489,15 +2463,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001027:
version "1.0.30001028"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001028.tgz#f2241242ac70e0fa9cda55c2776d32a0867971c2"
integrity sha512-Vnrq+XMSHpT7E+LWoIYhs3Sne8h9lx9YJV3acH3THNCwU/9zV93/ta4xVfzTtnqd3rvnuVpVjE3DFqf56tr3aQ==
caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001030:
version "1.0.30001030"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz#78076c4c6d67d3e41d6eb9399853fb27fe6e44ee"
integrity sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001030:
version "1.0.30001031"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001031.tgz#76f1bdd39e19567b855302f65102d9a8aaad5930"
integrity sha512-DpAP5a1NGRLgYfaNCaXIRyGARi+3tJA2quZXNNA1Du26VyVkqvy2tznNu5ANyN1Y5aX44QDotZSVSUSi2uMGjg==
capture-exit@^2.0.0:
version "2.0.0"
@ -3358,9 +3327,9 @@ data-urls@^2.0.0:
whatwg-url "^8.0.0"
date-fns@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.9.0.tgz#d0b175a5c37ed5f17b97e2272bbc1fa5aec677d2"
integrity sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA==
version "2.10.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.10.0.tgz#abd10604d8bafb0bcbd2ba2e9b0563b922ae4b6b"
integrity sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==
de-indent@^1.0.2:
version "1.0.2"
@ -3772,15 +3741,10 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
electron-to-chromium@^1.3.349:
version "1.3.356"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.356.tgz#fb985ee0f3023e6e11b97547ff3f738bdd8643d2"
integrity sha512-qW4YHMfOFjvx0jkSK2vjaHoLjk1+uJIV5tqtLDo7P5y3/kM8KQP23YBU0Y5fCSW4jIbDvEzeHDaY4+4vEaqqOw==
electron-to-chromium@^1.3.363:
version "1.3.364"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.364.tgz#524bd0cf9c45ba49c508fd3b731a07efbf310b1c"
integrity sha512-V6hyxQ9jzt6Jy6w8tAv4HHKhIaVS6psG/gmwtQ+2+itdkWMHJLHJ4m1sFep/fWkdKvfJcPXuywfnECRzfNa7gw==
version "1.3.367"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.367.tgz#48abffcaa6591051b612ae70ddc657763ede2662"
integrity sha512-GCHQreWs4zhKA48FNXCjvpV4kTnKoLu2PSAfKX394g34NPvTs2pPh1+jzWitNwhmOYI8zIqt36ulRVRZUgqlfA==
elliptic@^6.0.0:
version "6.5.2"
@ -4339,9 +4303,9 @@ fast-glob@^2.2.6:
micromatch "^3.1.10"
fast-glob@^3.0.3:
version "3.2.1"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.1.tgz#c5aaea632f92543b744bdcb19f11efd49e56c7b3"
integrity sha512-XObtOQLTl4EptWcBbO9O6wd17VlVf9YXYY/zuzuu7nZfTsv4BL3KupMAMUVzH88CUwWkI3uNHBfxtfU8PveVTQ==
version "3.2.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d"
integrity sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
@ -4361,11 +4325,11 @@ fast-levenshtein@~2.0.6:
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fastq@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2"
integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==
version "1.6.1"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.1.tgz#4570c74f2ded173e71cf0beb08ac70bb85826791"
integrity sha512-mpIH5sKYueh3YyeJwqtVo8sORi0CgtmkVbK6kZStpQlZBYQuTzG2CZ7idSiJuA7bY0SFCWUc5WIs+oYumGCQNw==
dependencies:
reusify "^1.0.0"
reusify "^1.0.4"
faye-websocket@^0.10.0:
version "0.10.0"
@ -4490,16 +4454,7 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
make-dir "^2.0.0"
pkg-dir "^3.0.0"
find-cache-dir@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874"
integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==
dependencies:
commondir "^1.0.1"
make-dir "^3.0.0"
pkg-dir "^4.1.0"
find-cache-dir@^3.2.0:
find-cache-dir@^3.0.0, find-cache-dir@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.0.tgz#4d74ed1fe9ef1731467ca24378e8f8f5c8b6ed11"
integrity sha512-PtXtQb7IrD8O+h6Cq1dbpJH5NzD8+9keN1zZ0YlpDzl1PwXEJEBj6u1Xa92t1Hwluoozd9TNKul5Hi2iqpsWwg==
@ -5078,9 +5033,9 @@ hoopy@^0.1.4:
integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
hosted-git-info@^2.1.4:
version "2.8.5"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
hpack.js@^2.1.6:
version "2.1.6"
@ -5108,9 +5063,9 @@ html-comment-regex@^1.1.0:
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
html-encoding-sniffer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.0.tgz#70b3b69bb5999f35d0d4495d79079f35630e71ae"
integrity sha512-Y9prnPKkM7FXxQevZ5UH8Z6aVTY0ede1tHquck5UxGmKWDshxXh95gSa2xXYjS8AsGO5iOvrCI5+GttRKnLdNA==
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==
dependencies:
whatwg-encoding "^1.0.5"
@ -5486,12 +5441,7 @@ ip@^1.1.0, ip@^1.1.5:
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
ipaddr.js@^1.5.2, ipaddr.js@^1.9.0:
ipaddr.js@1.9.1, ipaddr.js@^1.5.2, ipaddr.js@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
@ -6322,7 +6272,14 @@ lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.1
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
log-symbols@2.2.0, log-symbols@^2.2.0:
log-symbols@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
dependencies:
chalk "^2.4.2"
log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
@ -6391,7 +6348,7 @@ make-dir@^2.0.0:
pify "^4.0.1"
semver "^5.6.0"
make-dir@^3.0.0, make-dir@^3.0.2:
make-dir@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392"
integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==
@ -6710,9 +6667,9 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
minimist "0.0.8"
mocha@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.1.tgz#276186d35a4852f6249808c6dd4a1376cbf6c6ce"
integrity sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg==
version "7.1.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.0.tgz#c784f579ad0904d29229ad6cb1e2514e4db7d249"
integrity sha512-MymHK8UkU0K15Q/zX7uflZgVoRWiTjy0fXE/QjKts6mowUvGxOdPhZ2qj3b0iZdUrNZlW9LAIMFHB4IW+2b3EQ==
dependencies:
ansi-colors "3.2.3"
browser-stdout "1.3.1"
@ -6725,7 +6682,7 @@ mocha@^7.0.1:
growl "1.10.5"
he "1.2.0"
js-yaml "3.13.1"
log-symbols "2.2.0"
log-symbols "3.0.0"
minimatch "3.0.4"
mkdirp "0.5.1"
ms "2.1.1"
@ -6957,13 +6914,6 @@ node-modules-regexp@^1.0.0:
resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-releases@^1.1.49:
version "1.1.49"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.49.tgz#67ba5a3fac2319262675ef864ed56798bb33b93e"
integrity sha512-xH8t0LS0disN0mtRCh+eByxFPie+msJUBL/lJDBuap53QGiYPa9joh83K4pCZgWJ+2L4b9h88vCVdXQ60NO2bg==
dependencies:
semver "^6.3.0"
node-releases@^1.1.50:
version "1.1.50"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.50.tgz#803c40d2c45db172d0410e4efec83aa8c6ad0592"
@ -8143,12 +8093,12 @@ proto-list@~1.2.1:
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
proxy-addr@~2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
ipaddr.js "1.9.1"
prr@~1.0.1:
version "1.0.1"
@ -8286,9 +8236,9 @@ raw-body@2.4.0:
unpipe "1.0.0"
react-is@^16.12.0:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
version "16.13.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527"
integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==
read-pkg-up@^1.0.1:
version "1.0.1"
@ -8626,7 +8576,7 @@ retry@^0.12.0:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
reusify@^1.0.0:
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
@ -8669,9 +8619,9 @@ rsvp@^4.8.4:
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
version "2.4.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8"
integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==
dependencies:
is-promise "^2.1.0"
@ -9591,19 +9541,10 @@ terser-webpack-plugin@^2.3.4:
terser "^4.4.3"
webpack-sources "^1.4.3"
terser@^4.1.2, terser@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87"
integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
source-map-support "~0.5.12"
terser@^4.4.3:
version "4.6.4"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.4.tgz#40a0b37afbe5b57e494536815efa68326840fc00"
integrity sha512-5fqgBPLgVHZ/fVvqRhhUp9YUiGXhFJ9ZkrZWD9vQtFBR4QIGTnbsb+/kKqSqfgp3WnBwGWAFnedGTtmX1YTn0w==
terser@^4.1.2, terser@^4.4.3, terser@^4.6.3:
version "4.6.6"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.6.tgz#da2382e6cafbdf86205e82fb9a115bd664d54863"
integrity sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
@ -9792,9 +9733,9 @@ ts-pnp@^1.1.6:
integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==
tslib@^1.10.0, tslib@^1.9.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.0.tgz#f1f3528301621a53220d58373ae510ff747a66bc"
integrity sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg==
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
tty-browserify@0.0.0:
version "0.0.0"
@ -10452,9 +10393,9 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-
source-map "~0.6.1"
webpack@^4.0.0:
version "4.41.6"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.6.tgz#12f2f804bf6542ef166755050d4afbc8f66ba7e1"
integrity sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==
version "4.42.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.0.tgz#b901635dd6179391d90740a63c93f76f39883eb8"
integrity sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==
dependencies:
"@webassemblyjs/ast" "1.8.5"
"@webassemblyjs/helper-module-context" "1.8.5"
@ -10620,9 +10561,9 @@ wrappy@1:
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
write-file-atomic@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.1.tgz#558328352e673b5bb192cf86500d60b230667d4b"
integrity sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==
version "3.0.3"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
dependencies:
imurmurhash "^0.1.4"
is-typedarray "^1.0.0"

1
go.mod
View File

@ -22,6 +22,7 @@ require (
github.com/lib/pq v1.2.0 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/common v0.2.0
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect

4
go.sum
View File

@ -14,7 +14,9 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d h1:ZX0t+GA3MWiP7LWt5xWOphWRQd5JwL4VW5uLW83KM8g=
@ -123,6 +125,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@ -205,6 +208,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=

View File

@ -19,10 +19,15 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/checkins"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/groups"
"github.com/hunterlong/statping/types/incidents"
"github.com/hunterlong/statping/types/messages"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/null"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/types/users"
"github.com/hunterlong/statping/utils"
"net/http"
"time"
@ -38,21 +43,21 @@ type apiResponse struct {
}
func apiIndexHandler(r *http.Request) interface{} {
coreClone := *core.CoreApp
coreClone := core.App
var loggedIn bool
_, err := getJwtToken(r)
if err == nil {
loggedIn = true
}
coreClone.LoggedIn = loggedIn
return *coreClone.ToCore()
return coreClone
}
func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
var err error
core.CoreApp.ApiKey = utils.NewSHA1Hash(40)
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
err = database.Update(core.CoreApp)
core.App.ApiKey = utils.NewSHA1Hash(40)
core.App.ApiSecret = utils.NewSHA1Hash(40)
err = core.App.Update()
if err != nil {
sendErrorJson(err, w, r)
return
@ -71,7 +76,7 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r)
return
}
app := core.CoreApp
app := core.App
if c.Name != "" {
app.Name = c.Name
}
@ -90,9 +95,9 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
if c.Timezone != app.Timezone {
app.Timezone = c.Timezone
}
app.UseCdn = types.NewNullBool(c.UseCdn.Bool)
err = database.Update(app)
returnJson(core.CoreApp, w, r)
app.UseCdn = null.NewNullBool(c.UseCdn.Bool)
err = app.Update()
returnJson(core.App, w, r)
}
type cacheJson struct {
@ -135,45 +140,33 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
var objName string
var objId int64
switch v := obj.(type) {
case types.Servicer:
case *services.Service:
objName = "service"
objId = v.Model().Id
case *notifier.Notification:
objId = v.Id
case *notifications.Notification:
objName = "notifier"
objId = v.Id
case *core.Core, *types.Core:
case *core.Core:
objName = "core"
case *types.User:
case *users.User:
objName = "user"
objId = v.Id
case *core.User:
objName = "user"
objId = v.Id
case *types.Group:
case *groups.Group:
objName = "group"
objId = v.Id
case database.Grouper:
objName = "group"
objId = v.Model().Id
case *core.Checkin:
case *checkins.Checkin:
objName = "checkin"
objId = v.Id
case *core.CheckinHit:
case *checkins.CheckinHit:
objName = "checkin_hit"
objId = v.Id
case *types.Message:
case *messages.Message:
objName = "message"
objId = v.Id
case *core.Message:
objName = "message"
objId = v.Id
case *types.Checkin:
objName = "checkin"
objId = v.Id
case *core.Incident:
case *incidents.Incident:
objName = "incident"
objId = v.Id
case *core.IncidentUpdate:
case *incidents.IncidentUpdate:
objName = "incident_update"
objId = v.Id
default:

View File

@ -1,12 +1,14 @@
package handlers
import (
"encoding/json"
"fmt"
"github.com/hunterlong/statping/core"
_ "github.com/hunterlong/statping/notifiers"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io/ioutil"
@ -31,12 +33,12 @@ func init() {
dir = utils.Directory
}
func TestResetDatabase(t *testing.T) {
err := core.TmpRecords("handlers.db")
t.Log(err)
require.Nil(t, err)
require.NotNil(t, core.CoreApp)
}
//func TestResetDatabase(t *testing.T) {
// err := core.TmpRecords("handlers.db")
// t.Log(err)
// require.Nil(t, err)
// require.NotNil(t, core.CoreApp)
//}
func TestFailedHTTPServer(t *testing.T) {
err := RunHTTPServer("missinghost", 0)
@ -65,6 +67,12 @@ func TestSetupRoutes(t *testing.T) {
URL: "/api",
Method: "GET",
ExpectedStatus: 200,
FuncTest: func() error {
if core.CoreApp.Setup {
return errors.New("core has already been setup")
}
return nil
},
},
{
Name: "Statping Run Setup",
@ -73,7 +81,13 @@ func TestSetupRoutes(t *testing.T) {
Body: form.Encode(),
ExpectedStatus: 200,
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
ExpectedFiles: []string{dir + "/config.yml", dir + "/tmp/" + types.SqliteFilename},
ExpectedFiles: []string{dir + "/config.yml", dir + "/handlers/" + types.SqliteFilename},
FuncTest: func() error {
if !core.CoreApp.Setup {
return errors.New("core has not been setup")
}
return nil
},
}}
for _, v := range tests {
@ -94,7 +108,13 @@ func TestMainApiRoutes(t *testing.T) {
URL: "/api",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"description":"This data is only used to testing"`},
ExpectedContains: []string{`"description":"This is an awesome test"`},
FuncTest: func() error {
if !core.CoreApp.Setup {
return errors.New("database is not setup")
}
return nil
},
},
{
Name: "Statping Renew API Keys",
@ -107,6 +127,12 @@ func TestMainApiRoutes(t *testing.T) {
URL: "/api/clear_cache",
Method: "POST",
ExpectedStatus: 200,
FuncTest: func() error {
if len(CacheStorage.List()) != 0 {
return errors.New("cache was not reset")
}
return nil
},
},
{
Name: "404 Error Page",
@ -123,346 +149,7 @@ func TestMainApiRoutes(t *testing.T) {
}
}
func TestApiServiceRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping All Services",
URL: "/api/services",
Method: "GET",
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200,
},
{
Name: "Statping Service 1",
URL: "/api/services/1",
Method: "GET",
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Data",
URL: "/api/services/1/data",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Ping Data",
URL: "/api/services/1/ping",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Heatmap Data",
URL: "/api/services/1/heatmap",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Hits",
URL: "/api/services/1/hits",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Failures",
URL: "/api/services/1/failures",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Reorder Services",
URL: "/api/services/reorder",
Method: "POST",
Body: `[{"service":1,"order":1},{"service":5,"order":2},{"service":2,"order":3},{"service":3,"order":4},{"service":4,"order":5}]`,
ExpectedStatus: 200,
HttpHeaders: []string{"Content-Type=application/json"},
},
{
Name: "Statping Create Service",
URL: "/api/services",
HttpHeaders: []string{"Content-Type=application/json"},
Method: "POST",
Body: `{
"name": "New Service",
"domain": "https://statping.com",
"expected": "",
"expected_status": 200,
"check_interval": 30,
"type": "http",
"method": "GET",
"post_data": "",
"port": 0,
"timeout": 30,
"order_id": 0
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"service","method":"create"`},
},
{
Name: "Statping Update Service",
URL: "/api/services/1",
HttpHeaders: []string{"Content-Type=application/json"},
Method: "POST",
Body: `{
"name": "Updated New Service",
"domain": "https://google.com",
"expected": "",
"expected_status": 200,
"check_interval": 60,
"type": "http",
"method": "GET",
"post_data": "",
"port": 0,
"timeout": 10,
"order_id": 0
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"service","method":"update"`},
},
{
Name: "Statping Delete Service",
URL: "/api/services/1",
Method: "DELETE",
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"service","method":"delete"`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}
func TestGroupAPIRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Groups",
URL: "/api/groups",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Statping View Group",
URL: "/api/groups/1",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Statping Create Group",
URL: "/api/groups",
HttpHeaders: []string{"Content-Type=application/json"},
Body: `{
"name": "New Group",
"public": true
}`,
Method: "POST",
ExpectedStatus: 200,
},
{
Name: "Statping Delete Group",
URL: "/api/groups/1",
Method: "DELETE",
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}
func TestApiUsersRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping All Users",
URL: "/api/users",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Create User",
URL: "/api/users",
HttpHeaders: []string{"Content-Type=application/json"},
Method: "POST",
Body: `{
"username": "adminuser2",
"email": "info@adminemail.com",
"password": "passsword123",
"admin": true
}`,
ExpectedStatus: 200,
}, {
Name: "Statping View User",
URL: "/api/users/1",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Update User",
URL: "/api/users/1",
Method: "POST",
Body: `{
"username": "adminupdated",
"email": "info@email.com",
"password": "password12345",
"admin": true
}`,
ExpectedStatus: 200,
}, {
Name: "Statping Delete User",
URL: "/api/users/1",
Method: "DELETE",
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}
func TestApiNotifiersRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Notifiers",
URL: "/api/notifiers",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Mobile Notifier",
URL: "/api/notifier/mobile",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Update Notifier",
URL: "/api/notifier/mobile",
Method: "POST",
Body: `{
"method": "mobile",
"var1": "ExponentPushToken[ToBadIWillError123456]",
"enabled": true,
"limits": 55
}`,
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}
func TestMessagesApiRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Messages",
URL: "/api/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
Name: "Statping Create Message",
URL: "/api/messages",
Method: "POST",
Body: `{
"title": "API Message",
"description": "This is an example a upcoming message for a service!",
"start_on": "2022-11-17T03:28:16.323797-08:00",
"end_on": "2022-11-17T05:13:16.323798-08:00",
"service": 1,
"notify_users": true,
"notify_method": "email",
"notify_before": 6,
"notify_before_scale": "hour"
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"message","method":"create"`},
},
{
Name: "Statping View Message",
URL: "/api/messages/1",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
Name: "Statping Update Message",
URL: "/api/messages/1",
Method: "POST",
Body: `{
"title": "Updated Message",
"description": "This message was updated",
"start_on": "2022-11-17T03:28:16.323797-08:00",
"end_on": "2022-11-17T05:13:16.323798-08:00",
"service": 1,
"notify_users": true,
"notify_method": "email",
"notify_before": 3,
"notify_before_scale": "hour"
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"message","method":"update"`},
},
{
Name: "Statping Delete Message",
URL: "/api/messages/1",
Method: "DELETE",
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"message","method":"delete"`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}
func TestApiCheckinRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Checkins",
URL: "/api/checkins",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Create Checkin",
URL: "/api/checkin",
Method: "POST",
Body: `{
"service_id": 2,
"name": "Server Checkin",
"interval": 900,
"grace": 60
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"checkin","method":"create"`},
},
{
Name: "Statping Checkins",
URL: "/api/checkins",
Method: "GET",
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}
type HttpFuncTest func() error
// HTTPTest contains all the parameters for a HTTP Unit Test
type HTTPTest struct {
@ -474,6 +161,8 @@ type HTTPTest struct {
ExpectedContains []string
HttpHeaders []string
ExpectedFiles []string
FuncTest HttpFuncTest
ResponseLen int
}
// RunHTTPTest accepts a HTTPTest type to execute the HTTP request
@ -497,6 +186,8 @@ func RunHTTPTest(test HTTPTest, t *testing.T) (string, *testing.T, error) {
assert.Nil(t, err)
return "", t, err
}
defer rr.Result().Body.Close()
stringBody := string(body)
if test.ExpectedStatus != rr.Result().StatusCode {
assert.Equal(t, test.ExpectedStatus, rr.Result().StatusCode)
@ -512,5 +203,15 @@ func RunHTTPTest(test HTTPTest, t *testing.T) (string, *testing.T, error) {
assert.FileExists(t, v)
}
}
if test.FuncTest != nil {
err := test.FuncTest()
assert.Nil(t, err)
}
if test.ResponseLen != 0 {
var respArray []interface{}
err := json.Unmarshal(body, &respArray)
assert.Nil(t, err)
assert.Equal(t, test.ResponseLen, len(respArray))
}
return stringBody, t, err
}

View File

@ -19,44 +19,43 @@ import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/checkins"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"net"
"net/http"
)
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
checkins := database.AllCheckins()
returnJson(checkins, w, r)
chks := checkins.All()
returnJson(chks, w, r)
}
func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
checkin, err := database.CheckinByKey(vars["api"])
checkin, err := checkins.FindByAPI(vars["api"])
if err != nil {
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
return
}
out := checkin.Model()
returnJson(out, w, r)
returnJson(checkin, w, r)
}
func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
var checkin *core.Checkin
var checkin *checkins.Checkin
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&checkin)
if err != nil {
sendErrorJson(err, w, r)
return
}
service := core.SelectService(checkin.ServiceId)
if service == nil {
service, err := services.Find(checkin.ServiceId)
if err != nil {
sendErrorJson(fmt.Errorf("missing service_id field"), w, r)
return
}
_, err = checkin.Create()
checkin.ServiceId = service.Id
err = checkin.Create()
if err != nil {
sendErrorJson(err, w, r)
return
@ -66,42 +65,40 @@ func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
checkin, err := database.CheckinByKey(vars["api"])
checkin, err := checkins.FindByAPI(vars["api"])
if err != nil {
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
return
}
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
hit := &types.CheckinHit{
hit := &checkins.CheckinHit{
Checkin: checkin.Id,
From: ip,
CreatedAt: utils.Now().UTC(),
}
newCheck, err := database.Create(hit)
err = hit.Create()
if err != nil {
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
return
}
checkin.Failing = false
checkin.LastHit = utils.Timezoner(utils.Now().UTC(), core.CoreApp.Timezone)
sendJsonAction(newCheck.Id, "update", w, r)
checkin.LastHitTime = utils.Now().UTC()
sendJsonAction(hit.Id, "update", w, r)
}
func checkinDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
checkin := core.SelectCheckin(vars["api"])
if checkin == nil {
checkin, err := checkins.FindByAPI(vars["api"])
if err != nil {
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
return
}
if err := database.Delete(checkin); err != nil {
if err := checkin.Delete(); err != nil {
sendErrorJson(err, w, r)
return
}
checkin.Delete()
sendJsonAction(checkin, "delete", w, r)
}

41
handlers/checkins_test.go Normal file
View File

@ -0,0 +1,41 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestApiCheckinRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Checkins",
URL: "/api/checkins",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Create Checkin",
URL: "/api/checkin",
Method: "POST",
Body: `{
"service_id": 2,
"name": "Server Checkin",
"interval": 900,
"grace": 60
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"checkin","method":"create"`},
},
{
Name: "Statping Checkins",
URL: "/api/checkins",
Method: "GET",
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -19,9 +19,8 @@ import (
"encoding/json"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/users"
"github.com/hunterlong/statping/utils"
"net/http"
"os"
@ -184,7 +183,7 @@ func removeJwtToken(w http.ResponseWriter) {
})
}
func setJwtToken(user *types.User, w http.ResponseWriter) (JwtClaim, string) {
func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
expirationTime := time.Now().Add(72 * time.Hour)
jwtClaim := JwtClaim{
Username: user.Username,
@ -209,7 +208,7 @@ func apiLoginHandler(w http.ResponseWriter, r *http.Request) {
form := parseForm(r)
username := form.Get("username")
password := form.Get("password")
user, auth := core.AuthUser(username, password)
user, auth := users.AuthUser(username, password)
if auth {
utils.Log.Infoln(fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
_, token := setJwtToken(user, w)

View File

@ -13,24 +13,28 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
package handlers
import (
"encoding/json"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/checkins"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/groups"
"github.com/hunterlong/statping/types/messages"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/types/users"
)
// ExportChartsJs renders the charts for the index page
type ExportData struct {
Core *types.Core `json:"core"`
Services []*types.Service `json:"services"`
Messages []*types.Message `json:"messages"`
Checkins []*types.Checkin `json:"checkins"`
Users []*types.User `json:"users"`
Groups []*types.Group `json:"groups"`
Notifiers []types.AllNotifiers `json:"notifiers"`
Core *core.Core `json:"core"`
Services []*services.Service `json:"services"`
Messages []*messages.Message `json:"messages"`
Checkins []*checkins.Checkin `json:"checkins"`
Users []*users.User `json:"users"`
Groups []*groups.Group `json:"groups"`
Notifiers []core.AllNotifiers `json:"notifiers"`
}
// ExportSettings will export a JSON file containing all of the settings below:
@ -42,17 +46,14 @@ type ExportData struct {
// - Groups
// - Messages
func ExportSettings() ([]byte, error) {
users := database.AllUsers()
messages := database.AllMessages()
data := ExportData{
Core: CoreApp.Core,
Notifiers: CoreApp.Notifications,
//Checkins: database.AllCheckins(),
Users: users,
//Services: CoreApp.Services,
//Groups: SelectGroups(true, true),
Messages: messages,
Core: core.App,
//Notifiers: notifications.All(),
Checkins: checkins.All(),
Users: users.All(),
Services: services.All(),
Groups: groups.All(),
Messages: messages.All(),
}
export, err := json.Marshal(data)
return export, err

View File

@ -2,7 +2,9 @@ package handlers
import (
"encoding/json"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/utils"
"html/template"
"net/http"
"net/url"
@ -65,20 +67,20 @@ func serviceFromID(r *http.Request, object interface{}) error {
var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap {
return template.FuncMap{
"VERSION": func() string {
return core.VERSION
return core.App.Version
},
"CoreApp": func() core.Core {
c := *core.CoreApp
c := *core.App
if c.Name == "" {
c.Name = "Statping"
}
return c
},
"USE_CDN": func() bool {
return core.CoreApp.UseCdn.Bool
return core.App.UseCdn.Bool
},
"USING_ASSETS": func() bool {
return core.CoreApp.UsingAssets()
return source.UsingAssets(utils.Directory)
},
"BasePath": func() string {
return basePath

View File

@ -16,37 +16,30 @@
package handlers
import (
"encoding/json"
"errors"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/groups"
"github.com/hunterlong/statping/utils"
"github.com/pkg/errors"
"net/http"
)
func selectGroup(r *http.Request) (*groups.Group, error) {
vars := mux.Vars(r)
id := utils.ToInt(vars["id"])
return groups.Find(id)
}
// apiAllGroupHandler will show all the groups
func apiAllGroupHandler(r *http.Request) interface{} {
auth, admin := IsUser(r), IsAdmin(r)
groups := core.SelectGroups(admin, auth)
return flattenGroups(groups)
}
func flattenGroups(groups map[int64]*core.Group) []*types.Group {
var groupers []*types.Group
for _, group := range groups {
groupers = append(groupers, group.Group)
}
return groupers
return groups.SelectGroups(admin, auth)
}
// apiGroupHandler will show a single group
func apiGroupHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
group := core.SelectGroup(utils.ToInt(vars["id"]))
if group.Id == 0 {
sendErrorJson(errors.New("group not found"), w, r)
group, err := selectGroup(r)
if err != nil {
sendErrorJson(errors.Wrap(err, "group not found"), w, r)
return
}
returnJson(group, w, r)
@ -54,52 +47,54 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request) {
// apiGroupUpdateHandler will update a group
func apiGroupUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
group := core.SelectGroup(utils.ToInt(vars["id"]))
if group.Id == 0 {
sendErrorJson(errors.New("group not found"), w, r)
group, err := selectGroup(r)
if err != nil {
sendErrorJson(errors.Wrap(err, "group not found"), w, r)
return
}
decoder := json.NewDecoder(r.Body)
decoder.Decode(&group)
err := database.Update(group)
if err != nil {
if err := DecodeJSON(r, &group); err != nil {
sendErrorJson(err, w, r)
return
}
if err := group.Update(); err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(group, "update", w, r)
}
// apiCreateGroupHandler accepts a POST method to create new groups
func apiCreateGroupHandler(w http.ResponseWriter, r *http.Request) {
var group *core.Group
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&group)
if err != nil {
var group *groups.Group
if err := DecodeJSON(r, &group); err != nil {
sendErrorJson(err, w, r)
return
}
_, err = database.Create(group)
if err != nil {
if err := group.Create(); err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(group, "create", w, r)
}
// apiGroupDeleteHandler accepts a DELETE method to delete groups
func apiGroupDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
group := core.SelectGroup(utils.ToInt(vars["id"]))
if group.Id == 0 {
sendErrorJson(errors.New("group not found"), w, r)
group, err := selectGroup(r)
if err != nil {
sendErrorJson(errors.Wrap(err, "group not found"), w, r)
return
}
err := database.Delete(group)
if err != nil {
if err := group.Delete(); err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(group, "delete", w, r)
}
@ -111,12 +106,23 @@ type groupOrder struct {
func apiGroupReorderHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
var newOrder []*groupOrder
decoder := json.NewDecoder(r.Body)
decoder.Decode(&newOrder)
if err := DecodeJSON(r, &newOrder); err != nil {
sendErrorJson(err, w, r)
return
}
for _, g := range newOrder {
group := core.SelectGroup(g.Id)
group, err := groups.Find(g.Id)
if err != nil {
sendErrorJson(err, w, r)
return
}
group.Order = g.Order
database.Update(group)
if err := group.Update(); err != nil {
sendErrorJson(err, w, r)
return
}
}
returnJson(newOrder, w, r)
}

47
handlers/groups_test.go Normal file
View File

@ -0,0 +1,47 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestGroupAPIRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Groups",
URL: "/api/groups",
Method: "GET",
ExpectedStatus: 200,
ResponseLen: 3,
},
{
Name: "Statping View Group",
URL: "/api/groups/1",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Statping Create Group",
URL: "/api/groups",
HttpHeaders: []string{"Content-Type=application/json"},
Body: `{
"name": "New Group",
"public": true
}`,
Method: "POST",
ExpectedStatus: 200,
},
{
Name: "Statping Delete Group",
URL: "/api/groups/1",
Method: "DELETE",
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/hunterlong/statping/types/core"
"html/template"
"net/http"
"os"
@ -29,7 +30,6 @@ import (
"strings"
"time"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils"
)
@ -98,25 +98,24 @@ func RunHTTPServer(ip string, port int) error {
httpServer.SetKeepAlivesEnabled(false)
return httpServer.ListenAndServe()
}
return nil
}
// IsReadAuthenticated will allow Read Only authentication for some routes
func IsReadAuthenticated(r *http.Request) bool {
if !core.CoreApp.Setup {
if !core.App.Setup {
return false
}
var token string
query := r.URL.Query()
key := query.Get("api")
if subtle.ConstantTimeCompare([]byte(key), []byte(core.CoreApp.ApiSecret)) == 1 {
if subtle.ConstantTimeCompare([]byte(key), []byte(core.App.ApiSecret)) == 1 {
return true
}
tokens, ok := r.Header["Authorization"]
if ok && len(tokens) >= 1 {
token = tokens[0]
token = strings.TrimPrefix(token, "Bearer ")
if subtle.ConstantTimeCompare([]byte(token), []byte(core.CoreApp.ApiSecret)) == 1 {
if subtle.ConstantTimeCompare([]byte(token), []byte(core.App.ApiSecret)) == 1 {
return true
}
}
@ -129,10 +128,10 @@ func IsFullAuthenticated(r *http.Request) bool {
if os.Getenv("GO_ENV") == "test" {
return true
}
if core.CoreApp == nil {
if core.App == nil {
return true
}
if !core.CoreApp.Setup {
if !core.App.Setup {
return false
}
var token string
@ -140,7 +139,7 @@ func IsFullAuthenticated(r *http.Request) bool {
if ok && len(tokens) >= 1 {
token = tokens[0]
token = strings.TrimPrefix(token, "Bearer ")
if subtle.ConstantTimeCompare([]byte(token), []byte(core.CoreApp.ApiSecret)) == 1 {
if subtle.ConstantTimeCompare([]byte(token), []byte(core.App.ApiSecret)) == 1 {
return true
}
}
@ -185,7 +184,7 @@ func ScopeName(r *http.Request) string {
// IsAdmin returns true if the user session is an administrator
func IsAdmin(r *http.Request) bool {
if !core.CoreApp.Setup {
if !core.App.Setup {
return false
}
if os.Getenv("GO_ENV") == "test" {
@ -200,7 +199,7 @@ func IsAdmin(r *http.Request) bool {
// IsUser returns true if the user is registered
func IsUser(r *http.Request) bool {
if !core.CoreApp.Setup {
if !core.App.Setup {
return false
}
if os.Getenv("GO_ENV") == "test" {
@ -275,7 +274,7 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
// "safe": func(html string) template.HTML {
// return template.HTML(html)
// },
// "Services": func() []types.ServiceInterface {
// "Services": func() []services.ServiceInterface {
// return core.CoreApp.Services
// },
//})

View File

@ -3,38 +3,35 @@ package handlers
import (
"encoding/json"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/incidents"
"github.com/hunterlong/statping/utils"
"net/http"
)
func apiAllIncidentsHandler(w http.ResponseWriter, r *http.Request) {
incidents := database.AllIncidents()
returnJson(incidents, w, r)
inc := incidents.All()
returnJson(inc, w, r)
}
func apiCreateIncidentHandler(w http.ResponseWriter, r *http.Request) {
var incident *types.Incident
var incident *incidents.Incident
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&incident)
if err != nil {
sendErrorJson(err, w, r)
return
}
newIncident := core.ReturnIncident(incident)
obj, err := database.Create(newIncident)
err = incident.Create()
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(obj, "create", w, r)
sendJsonAction(incident, "create", w, r)
}
func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
incident, err := database.Incident(utils.ToInt(vars["id"]))
incident, err := incidents.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(err, w, r)
return
@ -47,22 +44,18 @@ func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
return
}
err = database.Update(&incident)
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(incident, "update", w, r)
updates := incident.Updates()
sendJsonAction(updates, "update", w, r)
}
func apiDeleteIncidentHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
incident, err := database.Incident(utils.ToInt(vars["id"]))
incident, err := incidents.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(err, w, r)
return
}
err = database.Delete(incident)
err = incident.Delete()
if err != nil {
sendErrorJson(err, w, r)
return

View File

@ -16,23 +16,24 @@
package handlers
import (
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/services"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
if !core.CoreApp.Setup {
if !core.App.Setup {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "base.gohtml", core.CoreApp, nil)
ExecuteResponse(w, r, "base.gohtml", core.App, nil)
}
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
health := map[string]interface{}{
"services": len(core.Services()),
"services": len(services.All()),
"online": true,
"setup": core.IsSetup(),
"setup": core.App.Setup,
}
returnJson(health, w, r)
}

View File

@ -3,54 +3,51 @@ package handlers
import (
"encoding/json"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core/integrations"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/integrations"
"net/http"
)
func findIntegration(r *http.Request) (*integrations.Integration, string, error) {
vars := mux.Vars(r)
name := vars["name"]
intgr, err := integrations.Find(name)
if err != nil {
return nil, "", err
}
return intgr, name, nil
}
func apiAllIntegrationsHandler(w http.ResponseWriter, r *http.Request) {
integrations := integrations.Integrations
returnJson(integrations, w, r)
inte := integrations.All()
returnJson(inte, w, r)
}
func apiIntegrationViewHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
intgr, err := integrations.Find(vars["name"])
intgr, _, err := findIntegration(r)
if err != nil {
sendErrorJson(err, w, r)
return
}
returnJson(intgr.Get(), w, r)
returnJson(intgr, w, r)
}
func apiIntegrationHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
intgr, err := integrations.Find(vars["name"])
intgr, _, err := findIntegration(r)
if err != nil {
sendErrorJson(err, w, r)
return
}
var intJson *types.Integration
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&intJson); err != nil {
if err := decoder.Decode(&intgr); err != nil {
sendErrorJson(err, w, r)
return
}
integration := intgr.Get()
integration.Enabled = intJson.Enabled
integration.Fields = intJson.Fields
if err := integrations.Update(integration); err != nil {
if err := intgr.Update(); err != nil {
sendErrorJson(err, w, r)
return
}
list, err := intgr.List()
if err != nil {
sendErrorJson(err, w, r)
return
}
returnJson(list, w, r)
returnJson(intgr, w, r)
}

View File

@ -16,30 +16,35 @@
package handlers
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/messages"
"github.com/hunterlong/statping/utils"
"net/http"
)
func getMessageByID(r *http.Request) (*messages.Message, int64, error) {
vars := mux.Vars(r)
num := utils.ToInt(vars["id"])
message, err := messages.Find(num)
if err != nil {
return nil, num, err
}
return message, num, nil
}
func apiAllMessagesHandler(r *http.Request) interface{} {
messages := database.AllMessages()
return messages
msgs := messages.All()
return msgs
}
func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
var message *types.Message
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&message)
if err != nil {
var message *messages.Message
if err := DecodeJSON(r, &message); err != nil {
sendErrorJson(err, w, r)
return
}
_, err = database.Create(message)
if err != nil {
if err := message.Create(); err != nil {
sendErrorJson(err, w, r)
return
}
@ -47,23 +52,21 @@ func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
}
func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message, err := database.Message(utils.ToInt(vars["id"]))
message, id, err := getMessageByID(r)
if err != nil {
sendErrorJson(err, w, r)
sendErrorJson(fmt.Errorf("message #%d was not found", id), w, r)
return
}
returnJson(message, w, r)
}
func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message, err := database.Message(utils.ToInt(vars["id"]))
message, id, err := getMessageByID(r)
if err != nil {
sendErrorJson(err, w, r)
sendErrorJson(fmt.Errorf("message #%d was not found", id), w, r)
return
}
err = database.Delete(message)
err = message.Delete()
if err != nil {
sendErrorJson(err, w, r)
return
@ -72,20 +75,16 @@ func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
}
func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message, err := database.Message(utils.ToInt(vars["id"]))
message, id, err := getMessageByID(r)
if err != nil {
sendErrorJson(fmt.Errorf("message #%v was not found", vars["id"]), w, r)
sendErrorJson(fmt.Errorf("message #%d was not found", id), w, r)
return
}
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&message)
if err != nil {
if err := DecodeJSON(r, &message); err != nil {
sendErrorJson(err, w, r)
return
}
err = database.Update(message)
if err != nil {
if err := message.Update(); err != nil {
sendErrorJson(err, w, r)
return
}

72
handlers/messages_test.go Normal file
View File

@ -0,0 +1,72 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestMessagesApiRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Messages",
URL: "/api/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
Name: "Statping Create Message",
URL: "/api/messages",
Method: "POST",
Body: `{
"title": "API Message",
"description": "This is an example a upcoming message for a service!",
"start_on": "2022-11-17T03:28:16.323797-08:00",
"end_on": "2022-11-17T05:13:16.323798-08:00",
"service": 1,
"notify_users": true,
"notify_method": "email",
"notify_before": 6,
"notify_before_scale": "hour"
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success"`, `"type":"message"`, `"method":"create"`, `"title": "API Message"`},
},
{
Name: "Statping View Message",
URL: "/api/messages/1",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
Name: "Statping Update Message",
URL: "/api/messages/1",
Method: "POST",
Body: `{
"title": "Updated Message",
"description": "This message was updated",
"start_on": "2022-11-17T03:28:16.323797-08:00",
"end_on": "2022-11-17T05:13:16.323798-08:00",
"service": 1,
"notify_users": true,
"notify_method": "email",
"notify_before": 3,
"notify_before_scale": "hour"
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success"`, `"type":"message"`, `"method":"update"`},
},
{
Name: "Statping Delete Message",
URL: "/api/messages/1",
Method: "DELETE",
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success"`, `"method":"delete"`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -6,7 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/utils"
"io"
"net/http"
@ -64,7 +64,7 @@ func basicAuthHandler(next http.Handler) http.Handler {
// apiMiddleware will confirm if Core has been setup
func apiMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !core.CoreApp.Setup {
if !core.App.Setup {
sendErrorJson(errors.New("statping has not been setup"), w, r)
return
}
@ -135,7 +135,7 @@ func cached(duration, contentType string, handler func(w http.ResponseWriter, r
content := CacheStorage.Get(r.RequestURI)
w.Header().Set("Content-Type", contentType)
w.Header().Set("Access-Control-Allow-Origin", "*")
if !core.IsSetup() {
if !core.App.Setup {
handler(w, r)
return
}

View File

@ -19,17 +19,17 @@ import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/null"
"github.com/hunterlong/statping/utils"
"net/http"
)
func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
var notifiers []*notifier.Notification
for _, n := range core.CoreApp.Notifications {
notif := n.(notifier.Notifier)
var notifiers []*notifications.Notification
for _, n := range core.App.Notifications {
notif := n.(notifications.Notifier)
notifiers = append(notifiers, notif.Select())
}
returnJson(notifiers, w, r)
@ -37,33 +37,36 @@ func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) {
func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
_, notifierObj, err := notifier.SelectNotifier(vars["notifier"])
notifier, err := notifications.Find(vars["notifier"])
if err != nil {
sendErrorJson(err, w, r)
return
}
returnJson(notifierObj, w, r)
returnJson(notifier, w, r)
}
func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
notifer, not, err := notifier.SelectNotifier(vars["notifier"])
notifer, err := notifications.Find(vars["notifier"])
if err != nil {
sendErrorJson(err, w, r)
return
}
n := notifer.Select()
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&notifer)
err = decoder.Decode(&n)
if err != nil {
sendErrorJson(err, w, r)
return
}
_, err = notifier.Update(not, notifer)
err = n.Update()
if err != nil {
sendErrorJson(err, w, r)
return
}
notifier.OnSave(notifer.Method)
notifications.OnSave(n.Method)
sendJsonAction(notifer, "update", w, r)
}
@ -83,48 +86,48 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
apiSecret := form.Get("api_secret")
limits := int(utils.ToInt(form.Get("limits")))
fakeNotifer, notif, err := notifier.SelectNotifier(method)
notifier, err := notifications.Find(method)
if err != nil {
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.App, "settings")
return
}
notifer := *fakeNotifer
n := notifier.Select()
if host != "" {
notifer.Host = host
n.Host = host
}
if port != 0 {
notifer.Port = port
n.Port = port
}
if username != "" {
notifer.Username = username
n.Username = username
}
if password != "" && password != "##########" {
notifer.Password = password
n.Password = password
}
if var1 != "" {
notifer.Var1 = var1
n.Var1 = var1
}
if var2 != "" {
notifer.Var2 = var2
n.Var2 = var2
}
if apiKey != "" {
notifer.ApiKey = apiKey
n.ApiKey = apiKey
}
if apiSecret != "" {
notifer.ApiSecret = apiSecret
n.ApiSecret = apiSecret
}
if limits != 0 {
notifer.Limits = limits
n.Limits = limits
}
notifer.Enabled = types.NewNullBool(enabled == "on")
n.Enabled = null.NewNullBool(enabled == "on")
err = notif.(notifier.Tester).OnTest()
if err == nil {
w.Write([]byte("ok"))
} else {
w.Write([]byte(err.Error()))
}
//err = notifications.OnTest(notifier)
//if err == nil {
// w.Write([]byte("ok"))
//} else {
// w.Write([]byte(err.Error()))
//}
}

View File

@ -0,0 +1,39 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestApiNotifiersRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Notifiers",
URL: "/api/notifiers",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Mobile Notifier",
URL: "/api/notifier/mobile",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Update Notifier",
URL: "/api/notifier/mobile",
Method: "POST",
Body: `{
"method": "mobile",
"var1": "ExponentPushToken[ToBadIWillError123456]",
"enabled": true,
"limits": 55
}`,
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -17,8 +17,8 @@ package handlers
import (
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/services"
"net/http"
"strings"
)
@ -35,21 +35,20 @@ import (
func prometheusHandler(w http.ResponseWriter, r *http.Request) {
metrics := []string{}
allFails := database.AllFailures()
allFails := failures.All()
system := fmt.Sprintf("statping_total_failures %v\n", allFails)
system += fmt.Sprintf("statping_total_services %v", len(core.Services()))
system += fmt.Sprintf("statping_total_services %v", len(services.All()))
metrics = append(metrics, system)
for _, ser := range core.Services() {
v := ser.Model()
for _, ser := range services.All() {
online := 1
if !v.Online {
if !ser.Online {
online = 0
}
met := fmt.Sprintf("statping_service_failures{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, v.Failures().Count())
met += fmt.Sprintf("statping_service_latency{id=\"%v\" name=\"%v\"} %0.0f\n", v.Id, v.Name, (v.Latency * 100))
met += fmt.Sprintf("statping_service_online{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, online)
met += fmt.Sprintf("statping_service_status_code{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, v.LastStatusCode)
met += fmt.Sprintf("statping_service_response_length{id=\"%v\" name=\"%v\"} %v", v.Id, v.Name, len([]byte(v.LastResponse)))
met := fmt.Sprintf("statping_service_failures{id=\"%v\" name=\"%v\"} %v\n", ser.Id, ser.Name, ser.AllFailures().Count())
met += fmt.Sprintf("statping_service_latency{id=\"%v\" name=\"%v\"} %0.0f\n", ser.Id, ser.Name, (ser.Latency * 100))
met += fmt.Sprintf("statping_service_online{id=\"%v\" name=\"%v\"} %v\n", ser.Id, ser.Name, online)
met += fmt.Sprintf("statping_service_status_code{id=\"%v\" name=\"%v\"} %v\n", ser.Id, ser.Name, ser.LastStatusCode)
met += fmt.Sprintf("statping_service_response_length{id=\"%v\" name=\"%v\"} %v", ser.Id, ser.Name, len([]byte(ser.LastResponse)))
metrics = append(metrics, met)
}
output := strings.Join(metrics, "\n")

View File

@ -1 +1,26 @@
package handlers
import (
"encoding/json"
"errors"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/utils"
"net/http"
)
func DecodeJSON(r *http.Request, obj interface{}) error {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&obj)
if err != nil {
return err
}
return nil
}
func GetID(r *http.Request) (int64, error) {
vars := mux.Vars(r)
if vars["id"] == "" {
return 0, errors.New("no id specified in request")
}
return utils.ToInt(vars["id"]), nil
}

View File

@ -18,8 +18,8 @@ package handlers
import (
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/utils"
"net/http"
)
@ -188,5 +188,5 @@ func resetRouter() {
}
func resetCookies() {
jwtKey = fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, utils.Now().Nanosecond())
jwtKey = fmt.Sprintf("%v_%v", core.App.ApiSecret, utils.Now().Nanosecond())
}

View File

@ -16,13 +16,13 @@
package handlers
import (
"encoding/json"
"errors"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/hits"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"github.com/pkg/errors"
"net/http"
)
@ -31,85 +31,83 @@ type serviceOrder struct {
Order int `json:"order"`
}
func serviceByID(r *http.Request) (*services.Service, error) {
vars := mux.Vars(r)
id := utils.ToInt(vars["id"])
servicer, err := services.Find(id)
if err != nil {
return nil, errors.Errorf("service %d not found", id)
}
return servicer, nil
}
func reorderServiceHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
var newOrder []*serviceOrder
decoder := json.NewDecoder(r.Body)
decoder.Decode(&newOrder)
if err := DecodeJSON(r, &newOrder); err != nil {
sendErrorJson(err, w, r)
return
}
for _, s := range newOrder {
service := core.SelectService(s.Id).Model()
service, err := services.Find(s.Id)
if err != nil {
sendErrorJson(errors.Errorf("service %d not found", s.Id), w, r)
return
}
service.Order = s.Order
database.Update(service)
service.Update()
}
returnJson(newOrder, w, r)
}
func apiServiceHandler(r *http.Request) interface{} {
vars := mux.Vars(r)
servicer := core.SelectService(utils.ToInt(vars["id"]))
if servicer == nil {
return errors.New("service not found")
service, err := serviceByID(r)
if err != nil {
return err
}
return servicer.Service
return service
}
func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
var service *types.Service
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&service)
if err != nil {
sendErrorJson(err, w, r)
return
}
_, err = database.Create(service)
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(service, "create", w, r)
}
func apiTestServiceHandler(w http.ResponseWriter, r *http.Request) {
var service *types.Service
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&service)
if err != nil {
var service *services.Service
if err := DecodeJSON(r, &service); err != nil {
sendErrorJson(err, w, r)
return
}
_, err = database.Create(service)
if err != nil {
if err := service.Create(); err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(service, "create", w, r)
}
func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.ToInt(vars["id"]))
if service == nil {
sendErrorJson(errors.New("service not found"), w, r)
return
}
decoder := json.NewDecoder(r.Body)
decoder.Decode(&service)
err := database.Update(service)
service, err := serviceByID(r)
if err != nil {
sendErrorJson(err, w, r)
return
}
go core.CheckService(service, true)
if err := DecodeJSON(r, &service); err != nil {
sendErrorJson(err, w, r)
return
}
err = service.Update()
if err != nil {
sendErrorJson(err, w, r)
return
}
go services.CheckService(service, true)
sendJsonAction(service, "update", w, r)
}
func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
srv := core.SelectService(utils.ToInt(vars["id"]))
service := srv.Model()
if service == nil {
sendErrorJson(errors.New("service not found"), w, r)
service, err := serviceByID(r)
if err != nil {
sendErrorJson(err, w, r)
return
}
if service.IsRunning() {
@ -122,13 +120,13 @@ func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) {
func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service, err := database.Service(utils.ToInt(vars["id"]))
service, err := services.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
return
}
groupQuery := database.ParseQueries(r, service.Hits())
groupQuery := database.ParseQueries(r, service.AllHits())
objs, err := groupQuery.GraphData(database.ByAverage("latency"))
if err != nil {
@ -140,13 +138,13 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service, err := database.Service(utils.ToInt(vars["id"]))
service, err := services.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
return
}
groupQuery := database.ParseQueries(r, service.Failures())
groupQuery := database.ParseQueries(r, service.AllFailures())
objs, err := groupQuery.GraphData(database.ByCount)
if err != nil {
@ -158,14 +156,13 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
}
func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service, err := database.Service(utils.ToInt(vars["id"]))
service, err := serviceByID(r)
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
return
}
groupQuery := database.ParseQueries(r, service.Hits())
groupQuery := database.ParseQueries(r, service.AllHits())
objs, err := groupQuery.GraphData(database.ByAverage("ping_time"))
if err != nil {
@ -176,85 +173,69 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
returnJson(objs, w, r)
}
type dataXy struct {
X int `json:"x"`
Y int `json:"y"`
}
type dataXyMonth struct {
Date string `json:"date"`
Data []*dataXy `json:"data"`
}
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.ToInt(vars["id"]))
if service == nil {
sendErrorJson(errors.New("service not found"), w, r)
return
}
err := database.Delete(service)
service, err := serviceByID(r)
if err != nil {
sendErrorJson(err, w, r)
return
}
err = service.Delete()
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(service, "delete", w, r)
}
func apiAllServicesHandler(r *http.Request) interface{} {
services := core.Services()
return joinServices(services)
}
func joinServices(srvs map[int64]*core.Service) []*types.Service {
var services []*types.Service
for _, v := range srvs {
v.UpdateStats()
services = append(services, v.Service)
}
services := services.All()
return services
}
func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
srv, err := database.Service(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(errors.New("service not found"), w, r)
return
func joinServices(srvss map[int64]*services.Service) []*services.Service {
var srvs []*services.Service
for _, v := range srvss {
v.UpdateStats()
srvs = append(srvs, v)
}
err = srv.Failures().DeleteAll()
return srvs
}
func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
service, err := serviceByID(r)
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(srv.Model(), "delete_failures", w, r)
err = service.DeleteFailures()
if err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(service, "delete_failures", w, r)
}
func apiServiceFailuresHandler(r *http.Request) interface{} {
vars := mux.Vars(r)
service, err := database.Service(utils.ToInt(vars["id"]))
service, err := services.Find(utils.ToInt(vars["id"]))
if err != nil {
return errors.New("service not found")
}
var fails []*types.Failure
database.ParseQueries(r, service.Failures()).Find(&fails)
var fails []*failures.Failure
database.ParseQueries(r, service.AllFailures()).Find(&fails)
return fails
}
func apiServiceHitsHandler(r *http.Request) interface{} {
vars := mux.Vars(r)
service, err := database.Service(utils.ToInt(vars["id"]))
service, err := services.Find(utils.ToInt(vars["id"]))
if err != nil {
return errors.New("service not found")
}
var hits []types.Hit
database.ParseQueries(r, service.Hits()).Find(&hits)
return hits
}
func createServiceHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "service_create.gohtml", core.CoreApp, nil)
var hts []*hits.Hit
database.ParseQueries(r, service.AllHits()).Find(&hts)
return hts
}

147
handlers/services_test.go Normal file
View File

@ -0,0 +1,147 @@
package handlers
import (
"github.com/hunterlong/statping/core"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"testing"
)
func TestApiServiceRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping All Services",
URL: "/api/services",
Method: "GET",
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200,
ResponseLen: 5,
FuncTest: func() error {
count := len(core.Services())
if count != 5 {
return errors.Errorf("incorrect services count: %d", count)
}
return nil
},
},
{
Name: "Statping Service 1",
URL: "/api/services/1",
Method: "GET",
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Data",
URL: "/api/services/1/hits_data",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Ping Data",
URL: "/api/services/1/ping_data",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Failure Data",
URL: "/api/services/1/failure_data",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Hits",
URL: "/api/services/1/hits_data",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Service 1 Failures",
URL: "/api/services/1/failure_data",
Method: "GET",
Body: "",
ExpectedStatus: 200,
},
{
Name: "Statping Reorder Services",
URL: "/api/services/reorder",
Method: "POST",
Body: `[{"service":1,"order":1},{"service":5,"order":2},{"service":2,"order":3},{"service":3,"order":4},{"service":4,"order":5}]`,
ExpectedStatus: 200,
HttpHeaders: []string{"Content-Type=application/json"},
},
{
Name: "Statping Create Service",
URL: "/api/services",
HttpHeaders: []string{"Content-Type=application/json"},
Method: "POST",
Body: `{
"name": "New Service",
"domain": "https://statping.com",
"expected": "",
"expected_status": 200,
"check_interval": 30,
"type": "http",
"method": "GET",
"post_data": "",
"port": 0,
"timeout": 30,
"order_id": 0
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success","type":"service","method":"create"`},
FuncTest: func() error {
count := len(core.Services())
if count != 6 {
return errors.Errorf("incorrect services count: %d", count)
}
return nil
},
},
{
Name: "Statping Update Service",
URL: "/api/services/1",
HttpHeaders: []string{"Content-Type=application/json"},
Method: "POST",
Body: `{
"name": "Updated New Service",
"domain": "https://google.com",
"expected": "",
"expected_status": 200,
"check_interval": 60,
"type": "http",
"method": "GET",
"post_data": "",
"port": 0,
"timeout": 10,
"order_id": 0
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success"`, `"name":"Updated New Service"`, `"method":"update"`},
},
{
Name: "Statping Delete Service",
URL: "/api/services/1",
Method: "DELETE",
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success"`, `"method":"delete"`},
FuncTest: func() error {
count := len(core.Services())
if count != 5 {
return errors.Errorf("incorrect services count: %d", count)
}
return nil
},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -17,9 +17,10 @@ package handlers
import (
"errors"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/configs"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/types/null"
"github.com/hunterlong/statping/types/users"
"github.com/hunterlong/statping/utils"
"net/http"
"strconv"
@ -28,7 +29,7 @@ import (
func processSetupHandler(w http.ResponseWriter, r *http.Request) {
var err error
if core.CoreApp.Setup {
if core.App.Setup {
sendErrorJson(errors.New("Statping has already been setup"), w, r)
return
}
@ -50,9 +51,8 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
domain := r.PostForm.Get("domain")
email := r.PostForm.Get("email")
sample, _ := strconv.ParseBool(r.PostForm.Get("sample_data"))
dir := utils.Directory
configs := &types.DbConfig{
confg := &configs.DbConfig{
DbConn: dbConn,
DbHost: dbHost,
DbUser: dbUser,
@ -69,68 +69,83 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
Location: utils.Directory,
}
log.WithFields(utils.ToFields(core.CoreApp, configs)).Debugln("new configs posted")
log.WithFields(utils.ToFields(core.App, confg)).Debugln("new configs posted")
if err := core.SaveConfig(configs); err != nil {
if err := confg.Save(utils.Directory); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
if _, err = core.LoadConfigFile(dir); err != nil {
if _, err = configs.LoadConfigs(); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
if err = core.CoreApp.Connect(configs, false, dir); err != nil {
if err = configs.ConnectConfigs(confg); err != nil {
log.Errorln(err)
core.DeleteConfig()
if err := confg.Delete(); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
}
}
if err = configs.MigrateDatabase(); err != nil {
sendErrorJson(err, w, r)
return
}
if err = core.CoreApp.DropDatabase(); err != nil {
sendErrorJson(err, w, r)
return
c := &core.Core{
Name: "Statping Sample Data",
Description: "This data is only used to testing",
//ApiKey: apiKey.(string),
//ApiSecret: apiSecret.(string),
Domain: "http://localhost:8080",
Version: "test",
CreatedAt: time.Now().UTC(),
UseCdn: null.NewNullBool(false),
Footer: null.NewNullString(""),
}
if err = core.CoreApp.CreateDatabase(); err != nil {
sendErrorJson(err, w, r)
return
}
core.CoreApp, err = core.InsertCore(configs)
if err != nil {
if err := c.Create(); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
admin := &types.User{
Username: configs.Username,
Password: configs.Password,
Email: configs.Email,
Admin: types.NewNullBool(true),
core.App = c
admin := &users.User{
Username: confg.Username,
Password: confg.Password,
Email: confg.Email,
Admin: null.NewNullBool(true),
}
if err := admin.Create(); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
database.Create(admin)
if sample {
if err = core.SampleData(); err != nil {
if err = configs.TriggerSamples(); err != nil {
sendErrorJson(err, w, r)
return
}
}
core.InitApp()
CacheStorage.Delete("/")
resetCookies()
time.Sleep(1 * time.Second)
out := struct {
Message string `json:"message"`
Config *types.DbConfig `json:"config"`
Message string `json:"message"`
Config *configs.DbConfig `json:"config"`
}{
"success",
configs,
confg,
}
returnJson(out, w, r)
}

View File

@ -16,20 +16,17 @@
package handlers
import (
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/users"
"github.com/hunterlong/statping/utils"
"net/http"
)
func apiUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
user, err := core.SelectUser(utils.ToInt(vars["id"]))
user, err := users.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(err, w, r)
return
@ -40,16 +37,22 @@ func apiUserHandler(w http.ResponseWriter, r *http.Request) {
func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
user, err := core.SelectUser(utils.ToInt(vars["id"]))
user, err := users.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(fmt.Errorf("user #%v was not found", vars["id"]), w, r)
return
}
decoder := json.NewDecoder(r.Body)
decoder.Decode(&user)
err = DecodeJSON(r, &user)
if err != nil {
sendErrorJson(fmt.Errorf("user #%v was not found", vars["id"]), w, r)
return
}
if user.Password != "" {
user.Password = utils.HashPassword(user.Password)
}
err = user.Update()
if err != nil {
sendErrorJson(fmt.Errorf("issue updating user #%v: %v", user.Id, err), w, r)
@ -60,12 +63,12 @@ func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
users := database.AllUsers()
if len(users) == 1 {
allUsers := users.All()
if len(allUsers) == 1 {
sendErrorJson(errors.New("cannot delete the last user"), w, r)
return
}
user, err := core.SelectUser(utils.ToInt(vars["id"]))
user, err := users.Find(utils.ToInt(vars["id"]))
if err != nil {
sendErrorJson(err, w, r)
return
@ -79,19 +82,20 @@ func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
}
func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
users := core.SelectAllUsers()
returnJson(users, w, r)
allUsers := users.All()
returnJson(allUsers, w, r)
}
func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
var user *types.User
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&user)
var user *users.User
err := DecodeJSON(r, &user)
if err != nil {
sendErrorJson(err, w, r)
return
}
_, err = database.Create(user)
err = user.Create()
if err != nil {
sendErrorJson(err, w, r)
return

57
handlers/users_test.go Normal file
View File

@ -0,0 +1,57 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestApiUsersRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping All Users",
URL: "/api/users",
Method: "GET",
ExpectedStatus: 200,
ResponseLen: 1,
}, {
Name: "Statping Create User",
URL: "/api/users",
HttpHeaders: []string{"Content-Type=application/json"},
Method: "POST",
Body: `{
"username": "adminuser2",
"email": "info@adminemail.com",
"password": "passsword123",
"admin": true
}`,
ExpectedStatus: 200,
}, {
Name: "Statping View User",
URL: "/api/users/1",
Method: "GET",
ExpectedStatus: 200,
}, {
Name: "Statping Update User",
URL: "/api/users/1",
Method: "POST",
Body: `{
"username": "adminupdated",
"email": "info@email.com",
"password": "password12345",
"admin": true
}`,
ExpectedStatus: 200,
}, {
Name: "Statping Delete User",
URL: "/api/users/1",
Method: "DELETE",
ExpectedStatus: 200,
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -13,14 +13,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
package integrators
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/integrations"
"github.com/hunterlong/statping/types/null"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"strconv"
"time"
@ -29,15 +31,15 @@ import (
const requiredSize = 17
type csvIntegration struct {
*types.Integration
*integrations.Integration
}
var CsvIntegrator = &csvIntegration{&types.Integration{
var CsvIntegrator = &csvIntegration{&integrations.Integration{
ShortName: "csv",
Name: "CSV File",
Icon: "<i class=\"fas fa-file-csv\"></i>",
Description: "Import multiple services from a CSV file. Please have your CSV file formatted with the correct amount of columns based on the <a href=\"https://raw.githubusercontent.com/hunterlong/statping/master/source/tmpl/bulk_import.csv\">example file on Github</a>.",
Fields: []*types.IntegrationField{
Fields: []*integrations.IntegrationField{
{
Name: "input",
Type: "textarea",
@ -48,11 +50,11 @@ var CsvIntegrator = &csvIntegration{&types.Integration{
var csvData [][]string
func (t *csvIntegration) Get() *types.Integration {
func (t *csvIntegration) Get() *integrations.Integration {
return t.Integration
}
func (t *csvIntegration) List() ([]*types.Service, error) {
func (t *csvIntegration) List() ([]*services.Service, error) {
data := Value(t, "input").(string)
buf := bytes.NewReader([]byte(data))
r := csv.NewReader(buf)
@ -61,7 +63,7 @@ func (t *csvIntegration) List() ([]*types.Service, error) {
return nil, err
}
var services []*types.Service
var services []*services.Service
for k, v := range records[1:] {
s, err := commaToService(v)
if err != nil {
@ -75,7 +77,7 @@ func (t *csvIntegration) List() ([]*types.Service, error) {
// commaToService will convert a CSV comma delimited string slice to a Service type
// this function is used for the bulk import services feature
func commaToService(s []string) (*types.Service, error) {
func commaToService(s []string) (*services.Service, error) {
if len(s) != requiredSize {
err := fmt.Errorf("file has %v columns of data, not the expected amount of %v columns for a service", len(s), requiredSize)
return nil, err
@ -106,23 +108,23 @@ func commaToService(s []string) (*types.Service, error) {
return nil, errors.New("could not parse verifiy SSL boolean: " + s[16])
}
newService := &types.Service{
newService := &services.Service{
Name: s[0],
Domain: s[1],
Expected: types.NewNullString(s[2]),
Expected: null.NewNullString(s[2]),
ExpectedStatus: int(utils.ToInt(s[3])),
Interval: int(utils.ToInt(interval.Seconds())),
Type: s[5],
Method: s[6],
PostData: types.NewNullString(s[7]),
PostData: null.NewNullString(s[7]),
Port: int(utils.ToInt(s[8])),
Timeout: int(utils.ToInt(timeout.Seconds())),
AllowNotifications: types.NewNullBool(allowNotifications),
Public: types.NewNullBool(public),
AllowNotifications: null.NewNullBool(allowNotifications),
Public: null.NewNullBool(public),
GroupId: int(utils.ToInt(s[13])),
Headers: types.NewNullString(s[14]),
Permalink: types.NewNullString(s[15]),
VerifySSL: types.NewNullBool(verifySsl),
Headers: null.NewNullString(s[14]),
Permalink: null.NewNullString(s[15]),
VerifySSL: null.NewNullBool(verifySsl),
}
return newService, nil

View File

@ -1,4 +1,4 @@
package integrations
package integrators
import (
"github.com/stretchr/testify/assert"

View File

@ -13,21 +13,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
package integrators
import (
"context"
dTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/integrations"
"github.com/hunterlong/statping/types/services"
"os"
)
type dockerIntegration struct {
*types.Integration
*integrations.Integration
}
var DockerIntegrator = &dockerIntegration{&types.Integration{
var DockerIntegrator = &dockerIntegration{&integrations.Integration{
ShortName: "docker",
Name: "Docker",
Icon: "<i class=\"fab fa-docker\"></i>",
@ -35,7 +36,7 @@ var DockerIntegrator = &dockerIntegration{&types.Integration{
You can also do this in Docker by setting <u>-v /var/run/docker.sock:/var/run/docker.sock</u> in the Statping Docker container.
All of the containers with open TCP/UDP ports will be listed for you to choose which services you want to add. If you running Statping inside of a container,
this container must be attached to all networks you want to communicate with.`,
Fields: []*types.IntegrationField{
Fields: []*integrations.IntegrationField{
{
Name: "path",
Description: "The absolute path to the Docker unix socket",
@ -53,11 +54,11 @@ this container must be attached to all networks you want to communicate with.`,
var cli *client.Client
func (t *dockerIntegration) Get() *types.Integration {
func (t *dockerIntegration) Get() *integrations.Integration {
return t.Integration
}
func (t *dockerIntegration) List() ([]*types.Service, error) {
func (t *dockerIntegration) List() ([]*services.Service, error) {
var err error
path := Value(t, "path").(string)
version := Value(t, "version").(string)
@ -69,7 +70,7 @@ func (t *dockerIntegration) List() ([]*types.Service, error) {
}
defer cli.Close()
var services []*types.Service
var srvs []*services.Service
containers, err := cli.ContainerList(context.Background(), dTypes.ContainerListOptions{})
if err != nil {
@ -86,7 +87,7 @@ func (t *dockerIntegration) List() ([]*types.Service, error) {
continue
}
service := &types.Service{
service := &services.Service{
Name: container.Names[0][1:],
Domain: v.IP,
Type: v.Type,
@ -95,9 +96,9 @@ func (t *dockerIntegration) List() ([]*types.Service, error) {
Timeout: 2,
}
services = append(services, service)
srvs = append(srvs, service)
}
}
return services, nil
return srvs, nil
}

View File

@ -1,4 +1,4 @@
package integrations
package integrators
import (
"github.com/stretchr/testify/assert"

View File

@ -13,19 +13,19 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
package integrators
import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/integrations"
"github.com/hunterlong/statping/utils"
)
var (
Integrations []types.Integrator
Integrations []integrations.Integrator
log = utils.Log.WithField("type", "integration")
db database.Database
)
@ -38,9 +38,17 @@ var (
// )
//}
func init() {
AddIntegrations(
CsvIntegrator,
TraefikIntegrator,
DockerIntegrator,
)
}
// integrationsDb returns the 'integrations' database column
func integrationsDb() database.Database {
return db.Model(&types.Integration{})
return db.Model(&integrations.Integration{})
}
// SetDB is called by core to inject the database for a integrator to use
@ -48,7 +56,7 @@ func SetDB(d database.Database) {
db = d
}
func Value(intg types.Integrator, fieldName string) interface{} {
func Value(intg integrations.Integrator, fieldName string) interface{} {
for _, v := range intg.Get().Fields {
if fieldName == v.Name {
return v.Value
@ -57,16 +65,16 @@ func Value(intg types.Integrator, fieldName string) interface{} {
return nil
}
func Update(integrator *types.Integration) error {
func Update(integrator *integrations.Integration) error {
fields := FieldsToJson(integrator)
fmt.Println(fields)
set := db.Model(&types.Integration{}).Where("name = ?", integrator.Name)
set := db.Model(&integrations.Integration{}).Where("name = ?", integrator.Name)
set.Set("enabled", integrator.Enabled)
set.Set("fields", fields)
return set.Error()
}
func FieldsToJson(integrator *types.Integration) string {
func FieldsToJson(integrator *integrations.Integration) string {
jsonData := make(map[string]interface{})
for _, v := range integrator.Fields {
jsonData[v.Name] = v.Value
@ -75,7 +83,7 @@ func FieldsToJson(integrator *types.Integration) string {
return string(data)
}
func JsonToFields(intg types.Integrator, input string) []*types.IntegrationField {
func JsonToFields(intg integrations.Integrator, input string) []*integrations.IntegrationField {
integrator := intg.Get()
var jsonData map[string]interface{}
json.Unmarshal([]byte(input), &jsonData)
@ -86,7 +94,7 @@ func JsonToFields(intg types.Integrator, input string) []*types.IntegrationField
return integrator.Fields
}
func SetFields(intg types.Integrator, data map[string][]string) (*types.Integration, error) {
func SetFields(intg integrations.Integrator, data map[string][]string) (*integrations.Integration, error) {
i := intg.Get()
for _, v := range i.Fields {
if data[v.Name] != nil {
@ -96,7 +104,7 @@ func SetFields(intg types.Integrator, data map[string][]string) (*types.Integrat
return i, nil
}
func Find(name string) (types.Integrator, error) {
func Find(name string) (integrations.Integrator, error) {
for _, i := range Integrations {
obj := i.Get()
if obj.ShortName == name {
@ -107,27 +115,27 @@ func Find(name string) (types.Integrator, error) {
}
// db will return the notifier database column/record
func integratorDb(n *types.Integration) database.Database {
return db.Model(&types.Integration{}).Where("name = ?", n.Name).Find(n)
func integratorDb(n *integrations.Integration) database.Database {
return db.Model(&integrations.Integration{}).Where("name = ?", n.Name).Find(n)
}
// isInDatabase returns true if the integration has already been installed
func isInDatabase(i types.Integrator) bool {
func isInDatabase(i integrations.Integrator) bool {
inDb := integratorDb(i.Get()).RecordNotFound()
return !inDb
}
// SelectIntegration returns the Notification struct from the database
func SelectIntegration(i types.Integrator) (*types.Integration, error) {
func SelectIntegration(i integrations.Integrator) (*integrations.Integration, error) {
integration := i.Get()
err := db.Model(&types.Integration{}).Where("name = ?", integration.Name).Scan(&integration)
err := db.Model(&integrations.Integration{}).Where("name = ?", integration.Name).Scan(&integration)
return integration, err.Error()
}
// AddIntegrations accept a Integrator interface to be added into the array
func AddIntegrations(integrations ...types.Integrator) error {
for _, i := range integrations {
if utils.IsType(i, new(types.Integrator)) {
func AddIntegrations(inte ...integrations.Integrator) error {
for _, i := range inte {
if utils.IsType(i, new(integrations.Integrator)) {
Integrations = append(Integrations, i)
err := install(i)
if err != nil {
@ -141,7 +149,7 @@ func AddIntegrations(integrations ...types.Integrator) error {
}
// install will check the database for the notification, if its not inserted it will insert a new record for it
func install(i types.Integrator) error {
func install(i integrations.Integrator) error {
inDb := isInDatabase(i)
log.WithField("installed", inDb).
WithFields(utils.ToFields(i)).
@ -157,7 +165,7 @@ func install(i types.Integrator) error {
}
// insertDatabase will create a new record into the database for the integrator
func insertDatabase(i types.Integrator) (string, error) {
func insertDatabase(i integrations.Integrator) (string, error) {
integrator := i.Get()
query := db.Create(integrator)
if query.Error() != nil {

View File

@ -13,26 +13,27 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integrations
package integrators
import (
"encoding/json"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/integrations"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"net/url"
"time"
)
type traefikIntegration struct {
*types.Integration
*integrations.Integration
}
var TraefikIntegrator = &traefikIntegration{&types.Integration{
var TraefikIntegrator = &traefikIntegration{&integrations.Integration{
ShortName: "traefik",
Name: "Traefik",
Icon: "<i class=\"fas fa-network-wired\"></i>",
Description: ``,
Fields: []*types.IntegrationField{
Fields: []*integrations.IntegrationField{
{
Name: "endpoint",
Description: "The URL for the traefik API Endpoint",
@ -52,13 +53,13 @@ var TraefikIntegrator = &traefikIntegration{&types.Integration{
},
}}
func (t *traefikIntegration) Get() *types.Integration {
func (t *traefikIntegration) Get() *integrations.Integration {
return t.Integration
}
func (t *traefikIntegration) List() ([]*types.Service, error) {
func (t *traefikIntegration) List() ([]*services.Service, error) {
var err error
var services []*types.Service
var services []*services.Service
endpoint := Value(t, "endpoint").(string)
@ -77,9 +78,9 @@ func (t *traefikIntegration) List() ([]*types.Service, error) {
return services, err
}
func fetchMethod(endpoint, method string) ([]*types.Service, error) {
func fetchMethod(endpoint, method string) ([]*services.Service, error) {
var traefikServices []traefikService
var services []*types.Service
var srvs []*services.Service
d, _, err := utils.HttpRequest(endpoint+"/api/"+method+"/services", "GET", nil, []string{}, nil, 10*time.Second, false)
if err != nil {
return nil, err
@ -97,7 +98,7 @@ func fetchMethod(endpoint, method string) ([]*types.Service, error) {
return nil, err
}
service := &types.Service{
service := &services.Service{
Name: s.Name,
Domain: url.Hostname(),
Port: int(utils.ToInt(url.Port())),
@ -105,11 +106,11 @@ func fetchMethod(endpoint, method string) ([]*types.Service, error) {
Interval: 60,
Timeout: 2,
}
services = append(services, service)
srvs = append(srvs, service)
}
}
return services, err
return srvs, err
}
type traefikService struct {

View File

@ -1,4 +1,4 @@
package integrations
package integrators
import (
"github.com/stretchr/testify/assert"

View File

@ -17,18 +17,19 @@ package notifiers
import (
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"strings"
"time"
)
type commandLine struct {
*notifier.Notification
*notifications.Notification
}
var Command = &commandLine{&notifier.Notification{
var Command = &commandLine{&notifications.Notification{
Method: "Command",
Title: "Shell Command",
Description: "Shell Command allows you to run a customized shell/bash Command on the local machine it's running on.",
@ -37,7 +38,7 @@ var Command = &commandLine{&notifier.Notification{
Delay: time.Duration(1 * time.Second),
Icon: "fas fa-terminal",
Host: "/bin/bash",
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Shell or Bash",
Placeholder: "/bin/bash",
@ -63,17 +64,17 @@ func runCommand(app string, cmd ...string) (string, string, error) {
return outStr, errStr, err
}
func (u *commandLine) Select() *notifier.Notification {
func (u *commandLine) Select() *notifications.Notification {
return u.Notification
}
// OnFailure for commandLine will trigger failing service
func (u *commandLine) OnFailure(s *types.Service, f *types.Failure) {
func (u *commandLine) OnFailure(s *services.Service, f *failures.Failure) {
u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var2)
}
// OnSuccess for commandLine will trigger successful service
func (u *commandLine) OnSuccess(s *types.Service) {
func (u *commandLine) OnSuccess(s *services.Service) {
if !s.Online {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var1)

View File

@ -20,18 +20,19 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"strings"
"time"
)
type discord struct {
*notifier.Notification
*notifications.Notification
}
var Discorder = &discord{&notifier.Notification{
var Discorder = &discord{&notifications.Notification{
Method: "discord",
Title: "discord",
Description: "Send notifications to your discord channel using discord webhooks. Insert your discord channel Webhook URL to receive notifications. Based on the <a href=\"https://discordapp.com/developers/docs/resources/Webhook\">discord webhooker API</a>.",
@ -40,7 +41,7 @@ var Discorder = &discord{&notifier.Notification{
Delay: time.Duration(5 * time.Second),
Host: "https://discordapp.com/api/webhooks/****/*****",
Icon: "fab fa-discord",
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "discord webhooker URL",
Placeholder: "Insert your Webhook URL here",
@ -55,18 +56,18 @@ func (u *discord) Send(msg interface{}) error {
return err
}
func (u *discord) Select() *notifier.Notification {
func (u *discord) Select() *notifications.Notification {
return u.Notification
}
// OnFailure will trigger failing service
func (u *discord) OnFailure(s *types.Service, f *types.Failure) {
func (u *discord) OnFailure(s *services.Service, f *failures.Failure) {
msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue)
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
}
// OnSuccess will trigger successful service
func (u *discord) OnSuccess(s *types.Service) {
func (u *discord) OnSuccess(s *services.Service) {
if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
var msg interface{}

View File

@ -20,8 +20,10 @@ import (
"crypto/tls"
"fmt"
"github.com/go-mail/mail"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/null"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"html/template"
"time"
@ -108,17 +110,17 @@ var (
)
type email struct {
*notifier.Notification
*notifications.Notification
}
var Emailer = &email{&notifier.Notification{
var Emailer = &email{&notifications.Notification{
Method: "email",
Title: "email",
Description: "Send emails via SMTP when services are online or offline.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Icon: "far fa-envelope",
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "SMTP Host",
Placeholder: "Insert your SMTP Host here.",
@ -178,7 +180,7 @@ type emailOutgoing struct {
}
// OnFailure will trigger failing service
func (u *email) OnFailure(s *types.Service, f *types.Failure) {
func (u *email) OnFailure(s *services.Service, f *failures.Failure) {
email := &emailOutgoing{
To: u.Var2,
Subject: fmt.Sprintf("Service %v is Failing", s.Name),
@ -190,7 +192,7 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) {
}
// OnSuccess will trigger successful service
func (u *email) OnSuccess(s *types.Service) {
func (u *email) OnSuccess(s *services.Service) {
if !s.Online || !s.SuccessNotified {
var msg string
msg = s.DownText
@ -207,7 +209,7 @@ func (u *email) OnSuccess(s *types.Service) {
}
}
func (u *email) Select() *notifier.Notification {
func (u *email) Select() *notifications.Notification {
return u.Notification
}
@ -220,7 +222,7 @@ func (u *email) OnSave() error {
// OnTest triggers when this notifier has been saved
func (u *email) OnTest() error {
testService := &types.Service{
testService := &services.Service{
Id: 1,
Name: "Example Service",
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
@ -230,7 +232,7 @@ func (u *email) OnTest() error {
Method: "GET",
Timeout: 20,
LastStatusCode: 200,
Expected: types.NewNullString("test example"),
Expected: null.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: utils.Now().Add(-24 * time.Hour),
}

View File

@ -17,8 +17,9 @@ package notifiers
import (
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"net/url"
"strings"
@ -30,17 +31,17 @@ const (
)
type lineNotifier struct {
*notifier.Notification
*notifications.Notification
}
var LineNotify = &lineNotifier{&notifier.Notification{
var LineNotify = &lineNotifier{&notifications.Notification{
Method: lineNotifyMethod,
Title: "LINE Notify",
Description: "LINE Notify will send notifications to your LINE Notify account when services are offline or online. Based on the <a href=\"https://notify-bot.line.me/doc/en/\">LINE Notify API</a>.",
Author: "Kanin Peanviriyakulkit",
AuthorUrl: "https://github.com/dogrocker",
Icon: "far fa-bell",
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Access Token",
Placeholder: "Insert your Line Notify Access Token here.",
@ -58,18 +59,18 @@ func (u *lineNotifier) Send(msg interface{}) error {
return err
}
func (u *lineNotifier) Select() *notifier.Notification {
func (u *lineNotifier) Select() *notifications.Notification {
return u.Notification
}
// OnFailure will trigger failing service
func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) {
func (u *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
}
// OnSuccess will trigger successful service
func (u *lineNotifier) OnSuccess(s *types.Service) {
func (u *lineNotifier) OnSuccess(s *services.Service) {
if !s.Online || !s.SuccessNotified {
var msg string
msg = s.DownText

View File

@ -19,8 +19,9 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"time"
)
@ -28,10 +29,10 @@ import (
const mobileIdentifier = "com.statping"
type mobilePush struct {
*notifier.Notification
*notifications.Notification
}
var Mobile = &mobilePush{&notifier.Notification{
var Mobile = &mobilePush{&notifications.Notification{
Method: "mobile",
Title: "Mobile Notifications",
Description: `Receive push notifications on your Mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the Mobile app setup in seconds.
@ -40,7 +41,7 @@ var Mobile = &mobilePush{&notifier.Notification{
AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(5 * time.Second),
Icon: "fas fa-mobile-alt",
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Device Identifiers",
Placeholder: "A list of your Mobile device push notification ID's.",
@ -55,11 +56,11 @@ var Mobile = &mobilePush{&notifier.Notification{
}}},
}
func (u *mobilePush) Select() *notifier.Notification {
func (u *mobilePush) Select() *notifications.Notification {
return u.Notification
}
func dataJson(s *types.Service, f *types.Failure) map[string]interface{} {
func dataJson(s *services.Service, f *failures.Failure) map[string]interface{} {
serviceId := "0"
if s != nil {
serviceId = utils.ToString(s.Id)
@ -83,7 +84,7 @@ func dataJson(s *types.Service, f *types.Failure) map[string]interface{} {
}
// OnFailure will trigger failing service
func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) {
func (u *mobilePush) OnFailure(s *services.Service, f *failures.Failure) {
data := dataJson(s, f)
msg := &pushArray{
Message: fmt.Sprintf("Your service '%v' is currently failing! Reason: %v", s.Name, f.Issue),
@ -95,7 +96,7 @@ func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) {
}
// OnSuccess will trigger successful service
func (u *mobilePush) OnSuccess(s *types.Service) {
func (u *mobilePush) OnSuccess(s *services.Service) {
data := dataJson(s, nil)
if !s.Online || !s.SuccessNotified {
var msgStr string

75
notifiers/notifiers.go Normal file
View File

@ -0,0 +1,75 @@
package notifiers
import (
"fmt"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/utils"
"strings"
)
var (
allowedVars = []string{"host", "username", "password", "port", "api_key", "api_secret", "var1", "var2"}
)
func checkNotifierForm(n notifications.Notifier) error {
notifier := n.Select()
for _, f := range notifier.Form {
contains := contains(f.DbField, allowedVars)
if !contains {
return fmt.Errorf("the DbField '%v' is not allowed, allowed vars: %v", f.DbField, allowedVars)
}
}
return nil
}
func contains(s string, arr []string) bool {
for _, v := range arr {
if strings.ToLower(s) == v {
return true
}
}
return false
}
// AddNotifier accept a Notifier interface to be added into the array
func AddNotifiers(notifiers ...notifications.Notifier) error {
for _, n := range notifiers {
if err := checkNotifierForm(n); err != nil {
return err
}
notifications.AllCommunications = append(notifications.AllCommunications, n)
if _, err := notifications.Init(n); err != nil {
return err
}
}
startAllNotifiers()
return nil
}
// startAllNotifiers will start the go routine for each loaded notifier
func startAllNotifiers() {
for _, comm := range notifications.AllCommunications {
if utils.IsType(comm, new(notifications.Notifier)) {
notify := comm.(notifications.Notifier)
if notify.Select().Enabled.Bool {
notify.Select().Close()
notify.Select().Start()
go notifications.Queue(notify)
}
}
}
}
func AttachNotifiers() error {
return AddNotifiers(
Command,
Discorder,
Emailer,
LineNotify,
Mobile,
Slacker,
Telegram,
Twilio,
Webhook,
)
}

View File

@ -32,7 +32,7 @@ var (
currentCount int
)
var TestService = &types.Service{
var TestService = &services.Service{
Id: 1,
Name: "Interpol - All The Rage Back Home",
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",

View File

@ -19,8 +19,9 @@ import (
"bytes"
"errors"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"strings"
"text/template"
@ -35,10 +36,10 @@ const (
)
type slack struct {
*notifier.Notification
*notifications.Notification
}
var Slacker = &slack{&notifier.Notification{
var Slacker = &slack{&notifications.Notification{
Method: slackMethod,
Title: "slack",
Description: "Send notifications to your slack channel when a service is offline. Insert your Incoming webhooker URL for your channel to receive notifications. Based on the <a href=\"https://api.slack.com/incoming-webhooks\">slack API</a>.",
@ -47,7 +48,7 @@ var Slacker = &slack{&notifier.Notification{
Delay: time.Duration(10 * time.Second),
Host: "https://webhooksurl.slack.com/***",
Icon: "fab fa-slack",
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Incoming webhooker Url",
Placeholder: "Insert your slack Webhook URL here.",
@ -69,7 +70,7 @@ func parseSlackMessage(id int64, temp string, data interface{}) error {
}
type slackMessage struct {
Service *types.Service
Service *services.Service
Template string
Time int64
Issue string
@ -82,7 +83,7 @@ func (u *slack) Send(msg interface{}) error {
return err
}
func (u *slack) Select() *notifier.Notification {
func (u *slack) Select() *notifications.Notification {
return u.Notification
}
@ -95,7 +96,7 @@ func (u *slack) OnTest() error {
}
// OnFailure will trigger failing service
func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
func (u *slack) OnFailure(s *services.Service, f *failures.Failure) {
message := slackMessage{
Service: s,
Template: failingTemplate,
@ -106,7 +107,7 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
}
// OnSuccess will trigger successful service
func (u *slack) OnSuccess(s *types.Service) {
func (u *slack) OnSuccess(s *services.Service) {
if !s.Online {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
message := slackMessage{

View File

@ -19,8 +19,9 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"net/url"
"strings"
@ -28,10 +29,10 @@ import (
)
type telegram struct {
*notifier.Notification
*notifications.Notification
}
var Telegram = &telegram{&notifier.Notification{
var Telegram = &telegram{&notifications.Notification{
Method: "telegram",
Title: "Telegram",
Description: "Receive notifications on your Telegram channel when a service has an issue. You must get a Telegram API token from the /botfather. Review the <a target=\"_blank\" href=\"http://techthoughts.info/how-to-create-a-telegram-bot-and-send-messages-via-api\">Telegram API Tutorial</a> to learn how to generate a new API Token.",
@ -39,7 +40,7 @@ var Telegram = &telegram{&notifier.Notification{
AuthorUrl: "https://github.com/hunterlong",
Icon: "fab fa-telegram-plane",
Delay: time.Duration(5 * time.Second),
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Telegram API Token",
Placeholder: "383810182:EEx829dtCeufeQYXG7CUdiQopqdmmxBPO7-s",
@ -56,7 +57,7 @@ var Telegram = &telegram{&notifier.Notification{
}}},
}
func (u *telegram) Select() *notifier.Notification {
func (u *telegram) Select() *notifications.Notification {
return u.Notification
}
@ -82,13 +83,13 @@ func (u *telegram) Send(msg interface{}) error {
}
// OnFailure will trigger failing service
func (u *telegram) OnFailure(s *types.Service, f *types.Failure) {
func (u *telegram) OnFailure(s *services.Service, f *failures.Failure) {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
}
// OnSuccess will trigger successful service
func (u *telegram) OnSuccess(s *types.Service) {
func (u *telegram) OnSuccess(s *services.Service) {
if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
var msg interface{}

View File

@ -19,8 +19,9 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"net/url"
"strings"
@ -28,10 +29,10 @@ import (
)
type twilio struct {
*notifier.Notification
*notifications.Notification
}
var Twilio = &twilio{&notifier.Notification{
var Twilio = &twilio{&notifications.Notification{
Method: "twilio",
Title: "Twilio",
Description: "Receive SMS text messages directly to your cellphone when a service is offline. You can use a Twilio test account with limits. This notifier uses the <a href=\"https://www.twilio.com/docs/usage/api\">Twilio API</a>.",
@ -39,7 +40,7 @@ var Twilio = &twilio{&notifier.Notification{
AuthorUrl: "https://github.com/hunterlong",
Icon: "far fa-comment-alt",
Delay: time.Duration(10 * time.Second),
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "Account SID",
Placeholder: "Insert your Twilio Account SID",
@ -66,7 +67,7 @@ var Twilio = &twilio{&notifier.Notification{
}}},
}
func (u *twilio) Select() *notifier.Notification {
func (u *twilio) Select() *notifications.Notification {
return u.Notification
}
@ -92,13 +93,13 @@ func (u *twilio) Send(msg interface{}) error {
}
// OnFailure will trigger failing service
func (u *twilio) OnFailure(s *types.Service, f *types.Failure) {
func (u *twilio) OnFailure(s *services.Service, f *failures.Failure) {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
}
// OnSuccess will trigger successful service
func (u *twilio) OnSuccess(s *types.Service) {
func (u *twilio) OnSuccess(s *services.Service) {
if !s.Online || !s.SuccessNotified {
u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
var msg string

View File

@ -18,8 +18,9 @@ package notifiers
import (
"bytes"
"fmt"
"github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/types/notifications"
"github.com/hunterlong/statping/types/services"
"github.com/hunterlong/statping/utils"
"io/ioutil"
"net/http"
@ -32,10 +33,10 @@ const (
)
type webhooker struct {
*notifier.Notification
*notifications.Notification
}
var Webhook = &webhooker{&notifier.Notification{
var Webhook = &webhooker{&notifications.Notification{
Method: webhookMethod,
Title: "HTTP webhooker",
Description: "Send a custom HTTP request to a specific URL with your own body, headers, and parameters.",
@ -43,7 +44,7 @@ var Webhook = &webhooker{&notifier.Notification{
AuthorUrl: "https://github.com/hunterlong",
Icon: "fas fa-code-branch",
Delay: time.Duration(1 * time.Second),
Form: []notifier.NotificationForm{{
Form: []notifications.NotificationForm{{
Type: "text",
Title: "HTTP Endpoint",
Placeholder: "http://webhookurl.com/JW2MCP4SKQP",
@ -87,11 +88,11 @@ func (w *webhooker) Send(msg interface{}) error {
return err
}
func (w *webhooker) Select() *notifier.Notification {
func (w *webhooker) Select() *notifications.Notification {
return w.Notification
}
func replaceBodyText(body string, s *types.Service, f *types.Failure) string {
func replaceBodyText(body string, s *services.Service, f *failures.Failure) string {
body = utils.ConvertInterface(body, s)
body = utils.ConvertInterface(body, f)
return body
@ -129,7 +130,7 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
}
func (w *webhooker) OnTest() error {
body := replaceBodyText(w.Var2, notifier.ExampleService, nil)
body := replaceBodyText(w.Var2, notifications.ExampleService, nil)
resp, err := w.sendHttpWebhook(body)
if err != nil {
return err
@ -141,13 +142,13 @@ func (w *webhooker) OnTest() error {
}
// OnFailure will trigger failing service
func (w *webhooker) OnFailure(s *types.Service, f *types.Failure) {
func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) {
msg := replaceBodyText(w.Var2, s, f)
w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg)
}
// OnSuccess will trigger successful service
func (w *webhooker) OnSuccess(s *types.Service) {
func (w *webhooker) OnSuccess(s *services.Service) {
if !s.Online {
w.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id))
msg := replaceBodyText(w.Var2, s, nil)

File diff suppressed because one or more lines are too long

View File

@ -1,88 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"time"
)
// Checkin struct will allow an application to send a recurring HTTP GET to confirm a service is online
type Checkin struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
Name string `gorm:"column:name" json:"name"`
Interval int64 `gorm:"column:check_interval" json:"interval"`
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
ApiKey string `gorm:"column:api_key" json:"api_key"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Running chan bool `gorm:"-" json:"-"`
Failing bool `gorm:"-" json:"failing"`
LastHit time.Time `gorm:"-" json:"last_hit"`
Hits []*CheckinHit `gorm:"-" json:"hits"`
Failures []*Failure `gorm:"-" json:"failures"`
}
// CheckinHit is a successful response from a Checkin
type CheckinHit struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Checkin int64 `gorm:"index;column:checkin" json:"-"`
From string `gorm:"column:from_location" json:"from"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}
// BeforeCreate for checkinHit will set CreatedAt to UTC
func (c *CheckinHit) BeforeCreate() (err error) {
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now().UTC()
}
return
}
func (s *Checkin) Expected() time.Duration {
last := s.LastHit
now := time.Now().UTC()
return time.Duration(now.Second() - last.Second())
}
func (s *Checkin) Period() time.Duration {
return time.Duration(s.Interval) * time.Second
}
// Start will create a channel for the checkin checking go routine
func (s *Checkin) Start() {
s.Running = make(chan bool)
}
// Close will stop the checkin routine
func (s *Checkin) Close() {
if s.IsRunning() {
close(s.Running)
}
}
// IsRunning returns true if the checkin go routine is running
func (s *Checkin) IsRunning() bool {
if s.Running == nil {
return false
}
select {
case <-s.Running:
return false
default:
return true
}
}

View File

@ -0,0 +1,53 @@
package checkins
import (
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/utils"
)
func DB() database.Database {
return database.DB().Model(&Checkin{})
}
func DBhits() database.Database {
return database.DB().Model(&CheckinHit{})
}
func Find(id int64) (*Checkin, error) {
var checkin *Checkin
db := DB().Where("id = ?", id).Find(&checkin)
return checkin, db.Error()
}
func FindByAPI(key string) (*Checkin, error) {
var checkin *Checkin
db := DB().Where("api = ?", key).Find(&checkin)
return checkin, db.Error()
}
func All() []*Checkin {
var checkins []*Checkin
DB().Find(&checkins)
return checkins
}
func (c *Checkin) Create() error {
c.ApiKey = utils.RandomString(7)
c.Start()
go c.CheckinRoutine()
db := DB().Create(&c)
return db.Error()
}
func (c *Checkin) Update() error {
db := DB().Update(&c)
return db.Error()
}
func (c *Checkin) Delete() error {
c.Close()
db := DB().Delete(&c)
return db.Error()
}

View File

@ -0,0 +1,29 @@
package checkins
func (c *Checkin) LastHit() *CheckinHit {
var hit *CheckinHit
DBhits().Where("checkin = ?", c.Id).Last(&hit)
return hit
}
func (c *Checkin) Hits() []*CheckinHit {
var hits []*CheckinHit
DBhits().Where("checkin = ?", c.Id).Find(&hits)
c.AllHits = hits
return hits
}
func (c *CheckinHit) Create() error {
db := DBhits().Create(&c)
return db.Error()
}
func (c *CheckinHit) Update() error {
db := DBhits().Update(&c)
return db.Error()
}
func (c *CheckinHit) Delete() error {
db := DBhits().Delete(&c)
return db.Error()
}

View File

@ -0,0 +1,23 @@
package checkins
import (
"github.com/hunterlong/statping/types/failures"
"time"
)
func (c *Checkin) CreateFailure(f *failures.Failure) error {
f.Checkin = c.Id
return failures.DB().Create(&f).Error()
}
func (c *Checkin) FailuresColumnID() (string, int64) {
return "checkin", c.Id
}
func (c *Checkin) Failures() failures.Failurer {
return failures.AllFailures(c)
}
func (c *Checkin) FailuresSince(t time.Time) failures.Failurer {
return failures.FailuresSince(t, c)
}

59
types/checkins/methods.go Normal file
View File

@ -0,0 +1,59 @@
package checkins
import (
"fmt"
"time"
)
func (c *Checkin) Expected() time.Duration {
last := c.LastHit()
now := time.Now().UTC()
lastDir := now.Sub(last.CreatedAt)
sub := time.Duration(c.Period() - lastDir)
return sub
}
func (c *Checkin) Period() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.Interval))
return duration
}
// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response)
func (c *Checkin) Grace() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod))
return duration
}
// Start will create a channel for the checkin checking go routine
func (c *Checkin) Start() {
c.Running = make(chan bool)
}
// Close will stop the checkin routine
func (c *Checkin) Close() {
if c.IsRunning() {
close(c.Running)
}
}
// IsRunning returns true if the checkin go routine is running
func (c *Checkin) IsRunning() bool {
if c.Running == nil {
return false
}
select {
case <-c.Running:
return false
default:
return true
}
}
// String will return a Checkin API string
func (c *Checkin) String() string {
return c.ApiKey
}
func (c *Checkin) Link() string {
return fmt.Sprintf("%v/checkin/%v", "DOMAINHERE", c.ApiKey)
}

60
types/checkins/routine.go Normal file
View File

@ -0,0 +1,60 @@
package checkins
import (
"fmt"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/utils"
"github.com/prometheus/common/log"
"time"
)
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
between := utils.Now().Sub(utils.Now()).Seconds()
if between > float64(c.Interval) {
fmt.Println("rechecking every 15 seconds!")
time.Sleep(15 * time.Second)
guard <- struct{}{}
c.RecheckCheckinFailure(guard)
} else {
fmt.Println("i recovered!!")
}
<-guard
}
// Routine for checking if the last Checkin was within its interval
func (c *Checkin) CheckinRoutine() {
lastHit := c.LastHit()
if lastHit == nil {
return
}
reCheck := c.Period()
CheckinLoop:
for {
select {
case <-c.Running:
log.Infoln(fmt.Sprintf("Stopping checkin routine: %v", c.Name))
c.Failing = false
break CheckinLoop
case <-time.After(reCheck):
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() <= 0 {
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, lastHit.CreatedAt)
log.Errorln(issue)
fail := &failures.Failure{
Issue: issue,
Method: "checkin",
Service: c.ServiceId,
Checkin: c.Id,
PingTime: c.Expected().Seconds(),
CreatedAt: time.Time{},
}
c.CreateFailure(fail)
}
reCheck = c.Period()
}
continue
}
}

47
types/checkins/samples.go Normal file
View File

@ -0,0 +1,47 @@
package checkins
import (
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/utils"
"time"
)
func (c *Checkin) Samples() []database.DbObject {
checkin1 := &Checkin{
Name: "Example Checkin 1",
ServiceId: 1,
Interval: 300,
GracePeriod: 300,
ApiKey: utils.RandomString(7),
}
checkin2 := &Checkin{
Name: "Example Checkin 2",
ServiceId: 2,
Interval: 900,
GracePeriod: 300,
ApiKey: utils.RandomString(7),
}
return []database.DbObject{checkin1, checkin2}
}
func (c *CheckinHit) Samples() []database.DbObject {
checkTime := time.Now().UTC().Add(-24 * time.Hour)
var hits []database.DbObject
for i := int64(1); i <= 2; i++ {
checkHit := &CheckinHit{
Checkin: i,
From: "192.168.0.1",
CreatedAt: checkTime.UTC(),
}
hits = append(hits, checkHit)
checkTime = checkTime.Add(10 * time.Minute)
}
return hits
}

55
types/checkins/struct.go Normal file
View File

@ -0,0 +1,55 @@
package checkins
import (
"github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types/failures"
"github.com/hunterlong/statping/utils"
"time"
)
// Checkin struct will allow an application to send a recurring HTTP GET to confirm a service is online
type Checkin struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
Name string `gorm:"column:name" json:"name"`
Interval int64 `gorm:"column:check_interval" json:"interval"`
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
ApiKey string `gorm:"column:api_key" json:"api_key"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Running chan bool `gorm:"-" json:"-"`
Failing bool `gorm:"-" json:"failing"`
LastHitTime time.Time `gorm:"-" json:"last_hit"`
AllHits []*CheckinHit `gorm:"-" json:"hits"`
AllFailures []*failures.Failure `gorm:"-" json:"failures"`
}
// CheckinHit is a successful response from a Checkin
type CheckinHit struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Checkin int64 `gorm:"index;column:checkin" json:"-"`
From string `gorm:"column:from_location" json:"from"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}
// BeforeCreate for checkinHit will set CreatedAt to UTC
func (c *CheckinHit) BeforeCreate() (err error) {
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now().UTC()
}
return
}
func (c *Checkin) BeforeCreate() (err error) {
c.ApiKey = utils.RandomString(7)
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now().UTC()
c.UpdatedAt = time.Now().UTC()
}
return
}
func (c *Checkin) BeforeDelete(tx database.Database) (err error) {
return tx.Where("id = ?", c.ServiceId).
Update("group_id", 0).Error()
}

View File

@ -0,0 +1,76 @@
package configs
import (
"github.com/hunterlong/statping/utils"
"github.com/joho/godotenv"
"github.com/pkg/errors"
)
func loadConfigEnvs() (*DbConfig, error) {
var err error
loadDotEnvs()
dbConn := utils.Getenv("DB_CONN", "").(string)
dbHost := utils.Getenv("DB_HOST", "").(string)
dbUser := utils.Getenv("DB_USER", "").(string)
dbPass := utils.Getenv("DB_PASS", "").(string)
dbData := utils.Getenv("DB_DATABASE", "").(string)
dbPort := utils.Getenv("DB_PORT", defaultPort(dbConn)).(int)
name := utils.Getenv("NAME", "Statping").(string)
desc := utils.Getenv("DESCRIPTION", "Statping Monitoring Sample Data").(string)
user := utils.Getenv("ADMIN_USER", "admin").(string)
password := utils.Getenv("ADMIN_PASS", "admin").(string)
domain := utils.Getenv("DOMAIN", "").(string)
sqlFile := utils.Getenv("SQL_FILE", "").(string)
if dbConn != "" && dbConn != "sqlite" {
if dbHost == "" {
return nil, errors.New("Missing DB_HOST environment variable")
}
if dbPort == 0 {
return nil, errors.New("Missing DB_PORT environment variable")
}
if dbUser == "" {
return nil, errors.New("Missing DB_USER environment variable")
}
if dbPass == "" {
return nil, errors.New("Missing DB_PASS environment variable")
}
if dbData == "" {
return nil, errors.New("Missing DB_DATABASE environment variable")
}
}
config := &DbConfig{
DbConn: dbConn,
DbHost: dbHost,
DbUser: dbUser,
DbPass: dbPass,
DbData: dbData,
DbPort: dbPort,
Project: name,
Description: desc,
Domain: domain,
Email: "",
Username: user,
Password: password,
Error: nil,
Location: utils.Directory,
SqlFile: sqlFile,
}
return config, err
}
// loadDotEnvs attempts to load database configs from a '.env' file in root directory
func loadDotEnvs() {
err := godotenv.Overload(utils.Directory + "/" + ".env")
if err == nil {
log.Warnln("Environment file '.env' found")
envs, _ := godotenv.Read(utils.Directory + "/" + ".env")
for k, e := range envs {
log.Infof("Overwriting %s=%s\n", k, e)
}
log.Warnln("These environment variables will overwrite any existing")
}
}

View File

@ -0,0 +1,26 @@
package configs
import (
"github.com/go-yaml/yaml"
"github.com/hunterlong/statping/types/core"
"github.com/hunterlong/statping/utils"
"github.com/pkg/errors"
)
func loadConfigFile(directory string) (*DbConfig, error) {
var configs *DbConfig
log.Debugln("Attempting to read config file at: " + directory + "/config.yml")
file, err := utils.OpenFile(directory + "/config.yml")
if err != nil {
core.App.Setup = false
return nil, errors.Wrapf(err, "config.yml file not found at %s/config.yml - starting in setup mode", directory)
}
err = yaml.Unmarshal([]byte(file), &configs)
if err != nil {
return nil, errors.Wrap(err, "yaml file not formatted correctly")
}
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + directory + "/config.yml")
return configs, nil
}

Some files were not shown because too many files have changed in this diff Show More