mirror of https://github.com/statping/statping
refactor
parent
29c332f0d9
commit
25913d6458
21
Makefile
21
Makefile
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
29
cmd/cli.go
29
cmd/cli.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{}, ¬ifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}, &integrations.Integration{}}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
121
cmd/main.go
121
cmd/main.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package main
|
||||
|
||||
// AttachNotifiers will attach all the notifier's into the system
|
154
core/checkin.go
154
core/checkin.go
|
@ -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
|
||||
}
|
216
core/configs.go
216
core/configs.go
|
@ -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
|
||||
}
|
175
core/core.go
175
core/core.go
|
@ -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
|
||||
}
|
|
@ -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://")
|
||||
}
|
410
core/database.go
410
core/database.go
|
@ -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{}, ¬ifier.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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
726
core/sample.go
726
core/sample.go
|
@ -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
|
||||
}
|
163
core/services.go
163
core/services.go
|
@ -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
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package core
|
115
core/users.go
115
core/users.go
|
@ -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
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
|
||||
type CheckinHitObj struct {
|
||||
hits []*types.CheckinHit
|
||||
o *Object
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
4
go.sum
|
@ -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=
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
// },
|
||||
//})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(¬ifer)
|
||||
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()))
|
||||
//}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
package integrations
|
||||
package integrators
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
|
@ -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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package integrations
|
||||
package integrators
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -1,4 +1,4 @@
|
|||
package integrations
|
||||
package integrators
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
|
@ -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{¬ifier.Notification{
|
||||
var Command = &commandLine{¬ifications.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{¬ifier.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)
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Discorder = &discord{¬ifications.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{¬ifier.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{}
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Emailer = &email{¬ifications.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),
|
||||
}
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var LineNotify = &lineNotifier{¬ifications.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
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Mobile = &mobilePush{¬ifications.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{¬ifier.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{¬ifier.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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Slacker = &slack{¬ifications.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{¬ifier.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{
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Telegram = &telegram{¬ifications.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{¬ifier.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{¬ifier.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{}
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Twilio = &twilio{¬ifications.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{¬ifier.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{¬ifier.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
|
||||
|
|
|
@ -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{¬ifier.Notification{
|
||||
var Webhook = &webhooker{¬ifications.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{¬ifier.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
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue