diff --git a/.travis.yml b/.travis.yml index f6f8dfe0..04ba6180 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ services: env: global: - - VERSION=0.29.2 + - VERSION=0.29.3 - DB_HOST=localhost - DB_USER=travis - DB_PASS= diff --git a/Dockerfile b/Dockerfile index 6725469a..3279b685 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -ENV VERSION=v0.29.2 +ENV VERSION=v0.29.3 RUN apk --no-cache add libstdc++ ca-certificates RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ diff --git a/cli.go b/cli.go index 9266f081..c762584b 100644 --- a/cli.go +++ b/cli.go @@ -3,8 +3,12 @@ package main import ( "fmt" "github.com/hunterlong/statup/core" + "github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/utils" "github.com/joho/godotenv" + "strings" + "time" + "upper.io/db.v3/sqlite" ) func CatchCLI(args []string) { @@ -18,6 +22,12 @@ func CatchCLI(args []string) { core.CompileSASS() case "api": HelpEcho() + case "test": + cmd := args[2] + switch cmd { + case "plugins": + LoadPlugins(true) + } case "export": var err error fmt.Printf("Statup v%v Exporting Static 'index.html' page...\n", VERSION) @@ -86,6 +96,7 @@ func HelpEcho() { fmt.Println(" statup - Main command to run Statup server") fmt.Println(" statup version - Returns the current version of Statup") fmt.Println(" statup run - Check all service 1 time and then quit") + fmt.Println(" statup test plugins - Test all plugins for required information") fmt.Println(" statup assets - Export all assets used locally to be edited.") fmt.Println(" statup env - Show all environment variables being used for Statup") fmt.Println(" statup export - Exports the index page as a static HTML for pushing") @@ -95,3 +106,80 @@ func HelpEcho() { fmt.Println(" statup help - Shows the user basic information about Statup") fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup") } + +func TestPlugin(plug plugin.PluginActions) { + RenderBoxes() + defer utils.DeleteFile("./.plugin_test.db") + core.CoreApp.AllPlugins = []plugin.PluginActions{plug} + info := plug.GetInfo() + utils.Log(1, "=======================================================================") + utils.Log(1, fmt.Sprintf(" Plugin Name: %v", info.Name)) + utils.Log(1, fmt.Sprintf(" Plugin Description: %v", info.Description)) + utils.Log(1, fmt.Sprintf(" Plugin Routes: %v", len(plug.Routes()))) + for k, r := range plug.Routes() { + utils.Log(1, fmt.Sprintf(" - Route %v - (%v) /%v", k+1, r.Method, r.URL)) + } + + fakeSrv := &core.Service{ + Id: 56, + Name: "Test Plugin Service", + Domain: "https://google.com", + } + + fakeFailD := core.FailureData{ + Issue: "No issue, just testing this plugin.", + } + + fakeCore := &core.Core{ + Name: "Plugin Test", + Description: "This is a fake Core for testing your plugin", + ApiSecret: "0x0x0x0x0", + ApiKey: "abcdefg12345", + Services: []*core.Service{fakeSrv}, + } + + fakeUser := &core.User{ + Id: 6334, + Username: "Bulbasaur", + Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy", + Email: "info@testdomain.com", + Admin: true, + CreatedAt: time.Now(), + } + + utils.Log(1, fmt.Sprintf("Creating a SQLite database for testing, will be deleted automatically...")) + sqlFake := sqlite.ConnectionURL{ + Database: "./.plugin_test.db", + } + fakeDb, err := sqlite.Open(sqlFake) + if err != nil { + utils.Log(3, err) + } + up, _ := core.SqlBox.String("sqlite_up.sql") + requests := strings.Split(up, ";") + for _, request := range requests { + _, err := fakeDb.Exec(request) + if err != nil { + utils.Log(2, err) + } + } + utils.Log(1, fmt.Sprintf("Finished creating Test SQLite database, sending events.")) + + utils.Log(1, "======> Sending 'OnLoad(sqlbuilder.Database)'") + core.OnLoad(fakeDb) + utils.Log(1, "======> Sending 'OnSuccess(Service)'") + core.OnSuccess(fakeSrv) + utils.Log(1, "======> Sending 'OnFailure(Service, FailureData)'") + core.OnFailure(fakeSrv, fakeFailD) + utils.Log(1, "======> Sending 'OnSettingsSaved(Core)'") + core.OnSettingsSaved(fakeCore) + utils.Log(1, "======> Sending 'OnNewService(Service)'") + core.OnNewService(fakeSrv) + utils.Log(1, "======> Sending 'OnNewUser(User)'") + core.OnNewUser(fakeUser) + utils.Log(1, "======> Sending 'OnUpdateService(Service)'") + core.OnUpdateService(fakeSrv) + utils.Log(1, "======> Sending 'OnDeletedService(Service)'") + core.OnDeletedService(fakeSrv) + +} diff --git a/core/checker.go b/core/checker.go index 668d5f34..976516e7 100644 --- a/core/checker.go +++ b/core/checker.go @@ -21,19 +21,26 @@ func CheckServices() { for _, v := range CoreApp.Services { obj := v //go obj.StartCheckins() + obj.stopRoutine = make(chan struct{}) go obj.CheckQueue() } } func (s *Service) CheckQueue() { - defer s.CheckQueue() - s.Check() - if s.Interval < 1 { - s.Interval = 1 + for { + select { + case <-s.stopRoutine: + return + default: + s.Check() + if s.Interval < 1 { + s.Interval = 1 + } + msg := fmt.Sprintf("Service: %v | Online: %v | Latency: %0.0fms", s.Name, s.Online, (s.Latency * 1000)) + utils.Log(1, msg) + time.Sleep(time.Duration(s.Interval) * time.Second) + } } - msg := fmt.Sprintf("Service: %v | Online: %v | Latency: %0.0fms", s.Name, s.Online, (s.Latency * 1000)) - utils.Log(1, msg) - time.Sleep(time.Duration(s.Interval) * time.Second) } func (s *Service) DNSCheck() (float64, error) { diff --git a/core/communication.go b/core/communication.go index 044d4a4e..9bccb2f6 100644 --- a/core/communication.go +++ b/core/communication.go @@ -61,6 +61,7 @@ func Create(c *types.Communication) (int64, error) { return 0, err } c.Id = uuid.(int64) + c.Routine = make(chan struct{}) if CoreApp != nil { CoreApp.Communications = append(CoreApp.Communications, c) } diff --git a/core/core.go b/core/core.go index f69112b6..3569dd98 100644 --- a/core/core.go +++ b/core/core.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "github.com/GeertJohan/go.rice" "github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/types" @@ -67,10 +66,6 @@ func InitApp() { func (c *Core) Update() (*Core, error) { res := DbSession.Collection("core").Find().Limit(1) err := res.Update(c) - CoreApp.Services, err = SelectAllServices() - - fmt.Println(CoreApp.Name, CoreApp.Description) - return c, err } diff --git a/core/events.go b/core/events.go index 30ee7deb..29796333 100644 --- a/core/events.go +++ b/core/events.go @@ -25,9 +25,12 @@ func OnFailure(s *Service, f FailureData) { for _, p := range CoreApp.AllPlugins { p.OnFailure(structs.Map(s)) } - - onFailureEmail(s, f) - onFailureSlack(s, f) + if notifications.SlackComm != nil { + onFailureSlack(s, f) + } + if notifications.EmailComm != nil { + onFailureEmail(s, f) + } } func onFailureSlack(s *Service, f FailureData) { diff --git a/core/services.go b/core/services.go index c2abe4ab..f2c660e3 100644 --- a/core/services.go +++ b/core/services.go @@ -32,7 +32,7 @@ type Service struct { OrderId int64 `json:"order_id"` Failures []*Failure `json:"failures"` Checkins []*Checkin `json:"checkins"` - runRoutine bool + stopRoutine chan struct{} LastResponse string LastStatusCode int LastOnline time.Time @@ -212,6 +212,11 @@ func (u *Service) Delete() error { utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", u.Name, err)) return err } + utils.Log(1, fmt.Sprintf("Stopping %v Monitoring...", u.Name)) + if u.stopRoutine != nil { + close(u.stopRoutine) + } + utils.Log(1, fmt.Sprintf("Stopped %v Monitoring Service", u.Name)) u.RemoveArray() OnDeletedService(u) return err @@ -237,8 +242,9 @@ func (u *Service) Create() (int64, error) { return 0, err } u.Id = uuid.(int64) + u.stopRoutine = make(chan struct{}) CoreApp.Services = append(CoreApp.Services, u) - //go u.CheckQueue() + go u.CheckQueue() OnNewService(u) return uuid.(int64), err } diff --git a/handlers/handlers.go b/handlers/handlers.go index 80d139a3..2856795d 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -26,9 +26,9 @@ func RunHTTPServer() { for _, p := range core.CoreApp.AllPlugins { info := p.GetInfo() for _, route := range p.Routes() { - path := fmt.Sprintf("/plugins/%v/%v", info.Name, route.URL) + path := fmt.Sprintf("%v", route.URL) r.Handle(path, http.HandlerFunc(route.Handler)).Methods(route.Method) - fmt.Printf("Added Route %v for plugin %v\n", path, info.Name) + utils.Log(1, fmt.Sprintf("Added Route %v for plugin %v\n", path, info.Name)) } } srv := &http.Server{ diff --git a/handlers/index.go b/handlers/index.go index 88a8d90e..887b3367 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -11,7 +11,7 @@ type index struct { } func IndexHandler(w http.ResponseWriter, r *http.Request) { - if core.CoreApp.Services == nil { + if core.CoreApp == nil { http.Redirect(w, r, "/setup", http.StatusSeeOther) return } diff --git a/main.go b/main.go index e9f05741..649d4f28 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "github.com/GeertJohan/go.rice" + "github.com/fatih/structs" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/handlers" "github.com/hunterlong/statup/plugin" @@ -68,7 +69,7 @@ func mainProcess() { core.InitApp() if !core.SetupMode { - LoadPlugins() + LoadPlugins(false) handlers.RunHTTPServer() } } @@ -81,14 +82,13 @@ func ForEachPlugin() { } } -func LoadPlugins() { +func LoadPlugins(debug bool) { utils.Log(1, fmt.Sprintf("Loading any available Plugins from /plugins directory")) if _, err := os.Stat("./plugins"); os.IsNotExist(err) { os.Mkdir("./plugins", os.ModePerm) } //ForEachPlugin() - files, err := ioutil.ReadDir("./plugins") if err != nil { utils.Log(2, fmt.Sprintf("Plugins directory was not found. Error: %v\n", err)) @@ -116,18 +116,30 @@ func LoadPlugins() { continue } + if debug { + utils.Log(1, fmt.Sprintf("Plugin '%v' struct:", f.Name())) + utils.Log(1, structs.Map(symPlugin)) + } + var plugActions plugin.PluginActions plugActions, ok := symPlugin.(plugin.PluginActions) if !ok { utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v", f.Name(), err)) + if debug { + //fmt.Println(symPlugin.(plugin.PluginActions)) + } continue } - plugActions.OnLoad(core.DbSession) - - core.CoreApp.Plugins = append(core.CoreApp.Plugins, plugActions.GetInfo()) - core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions) + if debug { + TestPlugin(plugActions) + } else { + plugActions.OnLoad(core.DbSession) + core.CoreApp.Plugins = append(core.CoreApp.Plugins, plugActions.GetInfo()) + core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions) + } + } + if !debug { + utils.Log(1, fmt.Sprintf("Loaded %v Plugins\n", len(core.CoreApp.Plugins))) } - - utils.Log(1, fmt.Sprintf("Loaded %v Plugins\n", len(core.CoreApp.Plugins))) } diff --git a/plugin/main.go b/plugin/main.go index c3f97941..8ddfc49c 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -37,6 +37,7 @@ func (p *PluginInfo) Form() string { type PluginActions interface { GetInfo() Info GetForm() string + OnLoad(sqlbuilder.Database) SetInfo(map[string]interface{}) Info Routes() []Routing OnSave(map[string]interface{}) @@ -52,7 +53,6 @@ type PluginActions interface { OnBeforeRequest(map[string]interface{}) OnAfterRequest(map[string]interface{}) OnShutdown() - OnLoad(sqlbuilder.Database) } type Routing struct { diff --git a/source/tmpl/index.html b/source/tmpl/index.html index 08a4e291..26f06a49 100644 --- a/source/tmpl/index.html +++ b/source/tmpl/index.html @@ -41,6 +41,14 @@
+{{ if not .Services }} + +{{end}} {{ range .Services }}
diff --git a/types/types.go b/types/types.go index 137bff11..7ef3364c 100644 --- a/types/types.go +++ b/types/types.go @@ -55,6 +55,7 @@ type Communication struct { Limits int64 `db:"limits" json:"-"` Removable bool `db:"removable" json:"-"` CreatedAt time.Time `db:"created_at" json:"created_at"` + Routine chan struct{} } type Email struct { diff --git a/utils/utils.go b/utils/utils.go index ca69b3d2..8a638b15 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "os" "regexp" "strconv" "strings" @@ -33,3 +34,12 @@ func UnderScoreString(str string) string { return newStr } + +func DeleteFile(file string) bool { + err := os.Remove(file) + if err != nil { + Log(3, err) + return false + } + return true +}