diff --git a/core/core.go b/core/core.go index c59eb00a..e4db19c0 100644 --- a/core/core.go +++ b/core/core.go @@ -64,9 +64,11 @@ func (c *Core) ToCore() *types.Core { func InitApp() { SelectCore() InsertNotifierDB() + InsertIntegratorDB() CoreApp.SelectAllServices(true) checkServices() AttachNotifiers() + AddIntegrations() CoreApp.Notifications = notifier.AllCommunications CoreApp.Integrations = integrations.Integrations go DatabaseMaintence() @@ -85,6 +87,18 @@ func InsertNotifierDB() error { return nil } +// InsertIntegratorDB inject the Statping database instance to the Integrations package +func InsertIntegratorDB() error { + if DbSession == nil { + err := CoreApp.Connect(false, utils.Directory) + if err != nil { + return errors.New("database connection has not been created") + } + } + integrations.SetDB(DbSession) + return nil +} + // UpdateCore will update the CoreApp variable inside of the 'core' table in database func UpdateCore(c *Core) (*Core, error) { db := coreDB().Update(&c) @@ -198,6 +212,15 @@ func AttachNotifiers() error { ) } +// AddIntegrations will attach all the integrations into the system +func AddIntegrations() error { + return integrations.AddIntegrations( + integrations.CsvIntegrator, + integrations.TraefikIntegrator, + integrations.DockerIntegrator, + ) +} + // ServiceOrder will reorder the services based on 'order_id' (Order) type ServiceOrder []types.ServiceInterface diff --git a/core/database.go b/core/database.go index 11c99a5c..bc29722d 100644 --- a/core/database.go +++ b/core/database.go @@ -37,7 +37,7 @@ var ( ) 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{}} + 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{}} gorm.NowFunc = func() time.Time { return time.Now().UTC() diff --git a/core/incidents.go b/core/incidents.go index 7db8e2fe..42631723 100644 --- a/core/incidents.go +++ b/core/incidents.go @@ -13,6 +13,18 @@ type IncidentUpdate struct { *types.IncidentUpdate } +// ReturnIncident returns *core.Incident based off a *types.Incident +func ReturnIncident(u *types.Incident) *Incident { + return &Incident{u} +} + +// SelectIncident returns the Incident based on the Incident's ID. +func SelectIncident(id int64) (*Incident, error) { + var incident Incident + err := incidentsDB().Where("id = ?", id).First(&incident) + return &incident, err.Error() +} + // AllIncidents will return all incidents and updates recorded func AllIncidents() []*Incident { var incidents []*Incident diff --git a/core/integrations/csv_file.go b/core/integrations/csv_file.go index c3843d23..89820320 100644 --- a/core/integrations/csv_file.go +++ b/core/integrations/csv_file.go @@ -32,7 +32,7 @@ type csvIntegration struct { *types.Integration } -var csvIntegrator = &csvIntegration{&types.Integration{ +var CsvIntegrator = &csvIntegration{&types.Integration{ ShortName: "csv", Name: "CSV File", Icon: "", diff --git a/core/integrations/csv_file_test.go b/core/integrations/csv_file_test.go index e6aaf969..89f0e693 100644 --- a/core/integrations/csv_file_test.go +++ b/core/integrations/csv_file_test.go @@ -14,23 +14,23 @@ func TestCsvFileIntegration(t *testing.T) { t.Run("Set Field Value", func(t *testing.T) { formPost := map[string][]string{} formPost["input"] = []string{string(data)} - _, err = SetFields(csvIntegrator, formPost) + _, err = SetFields(CsvIntegrator, formPost) require.Nil(t, err) }) t.Run("Get Field Value", func(t *testing.T) { - value := Value(csvIntegrator, "input").(string) + value := Value(CsvIntegrator, "input").(string) assert.Equal(t, string(data), value) }) t.Run("List Services from CSV File", func(t *testing.T) { - services, err := csvIntegrator.List() + services, err := CsvIntegrator.List() require.Nil(t, err) assert.Equal(t, 10, len(services)) }) t.Run("Confirm Services from CSV File", func(t *testing.T) { - services, err := csvIntegrator.List() + services, err := CsvIntegrator.List() require.Nil(t, err) assert.Equal(t, "Bulk Upload", services[0].Name) assert.Equal(t, "http://google.com", services[0].Domain) diff --git a/core/integrations/docker.go b/core/integrations/docker.go index ce5d40fa..febd5820 100644 --- a/core/integrations/docker.go +++ b/core/integrations/docker.go @@ -27,7 +27,7 @@ type dockerIntegration struct { *types.Integration } -var dockerIntegrator = &dockerIntegration{&types.Integration{ +var DockerIntegrator = &dockerIntegration{&types.Integration{ ShortName: "docker", Name: "Docker", Icon: "", diff --git a/core/integrations/docker_test.go b/core/integrations/docker_test.go index 820fb785..49bb7f58 100644 --- a/core/integrations/docker_test.go +++ b/core/integrations/docker_test.go @@ -12,25 +12,25 @@ func TestDockerIntegration(t *testing.T) { formPost := map[string][]string{} formPost["path"] = []string{"unix:///var/run/docker.sock"} formPost["version"] = []string{"1.25"} - _, err := SetFields(csvIntegrator, formPost) + _, err := SetFields(CsvIntegrator, formPost) require.Nil(t, err) }) t.Run("Get Field Value", func(t *testing.T) { - path := Value(dockerIntegrator, "path").(string) - version := Value(dockerIntegrator, "version").(string) + path := Value(DockerIntegrator, "path").(string) + version := Value(DockerIntegrator, "version").(string) assert.Equal(t, "unix:///var/run/docker.sock", path) assert.Equal(t, "1.25", version) }) t.Run("List Services from Docker", func(t *testing.T) { - services, err := dockerIntegrator.List() + services, err := DockerIntegrator.List() require.Nil(t, err) assert.Equal(t, 0, len(services)) }) t.Run("Confirm Services from Docker", func(t *testing.T) { - services, err := dockerIntegrator.List() + services, err := DockerIntegrator.List() require.Nil(t, err) for _, s := range services { t.Log(s) diff --git a/core/integrations/integrations.go b/core/integrations/integrations.go index fd3f28e7..5071a689 100644 --- a/core/integrations/integrations.go +++ b/core/integrations/integrations.go @@ -16,7 +16,9 @@ package integrations import ( + "encoding/json" "errors" + "fmt" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" ) @@ -24,14 +26,25 @@ import ( var ( Integrations []types.Integrator log = utils.Log.WithField("type", "integration") + db types.Database ) -func init() { - Integrations = append(Integrations, - csvIntegrator, - dockerIntegrator, - traefikIntegrator, - ) +//func init() { +// Integrations = append(Integrations, +// CsvIntegrator, +// DockerIntegrator, +// TraefikIntegrator, +// ) +//} + +// integrationsDb returns the 'integrations' database column +func integrationsDb() types.Database { + return db.Model(&types.Integration{}) +} + +// SetDB is called by core to inject the database for a integrator to use +func SetDB(d types.Database) { + db = d } func Value(intg types.Integrator, fieldName string) interface{} { @@ -43,6 +56,35 @@ func Value(intg types.Integrator, fieldName string) interface{} { return nil } +func Update(integrator *types.Integration) error { + fields := FieldsToJson(integrator) + fmt.Println(fields) + set := db.Model(&types.Integration{}).Where("name = ?", integrator.Name) + set.Set("enabled", integrator.Enabled) + set.Set("fields", fields) + return set.Error() +} + +func FieldsToJson(integrator *types.Integration) string { + jsonData := make(map[string]interface{}) + for _, v := range integrator.Fields { + jsonData[v.Name] = v.Value + } + data, _ := json.Marshal(jsonData) + return string(data) +} + +func JsonToFields(intg types.Integrator, input string) []*types.IntegrationField { + integrator := intg.Get() + var jsonData map[string]interface{} + json.Unmarshal([]byte(input), &jsonData) + + for _, v := range integrator.Fields { + v.Value = jsonData[v.Name] + } + return integrator.Fields +} + func SetFields(intg types.Integrator, data map[string][]string) (*types.Integration, error) { i := intg.Get() for _, v := range i.Fields { @@ -62,3 +104,63 @@ func Find(name string) (types.Integrator, error) { } return nil, errors.New(name + " not found") } + +// db will return the notifier database column/record +func integratorDb(n *types.Integration) types.Database { + return db.Model(&types.Integration{}).Where("name = ?", n.Name).Find(n) +} + +// isInDatabase returns true if the integration has already been installed +func isInDatabase(i types.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) { + integration := i.Get() + err := db.Model(&types.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)) { + Integrations = append(Integrations, i) + err := install(i) + if err != nil { + return err + } + } else { + return errors.New("notifier does not have the required methods") + } + } + return nil +} + +// 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 { + inDb := isInDatabase(i) + log.WithField("installed", inDb). + WithFields(utils.ToFields(i)). + Debugln(fmt.Sprintf("Checking if integrator '%v' is installed: %v", i.Get().Name, inDb)) + if !inDb { + _, err := insertDatabase(i) + if err != nil { + log.Errorln(err) + return err + } + } + return nil +} + +// insertDatabase will create a new record into the database for the integrator +func insertDatabase(i types.Integrator) (string, error) { + integrator := i.Get() + query := db.Create(integrator) + if query.Error() != nil { + return "", query.Error() + } + return integrator.Name, query.Error() +} diff --git a/core/integrations/traefik.go b/core/integrations/traefik.go index 46139de6..d7d98671 100644 --- a/core/integrations/traefik.go +++ b/core/integrations/traefik.go @@ -27,7 +27,7 @@ type traefikIntegration struct { *types.Integration } -var traefikIntegrator = &traefikIntegration{&types.Integration{ +var TraefikIntegrator = &traefikIntegration{&types.Integration{ ShortName: "traefik", Name: "Traefik", Icon: "", diff --git a/core/integrations/traefik_test.go b/core/integrations/traefik_test.go index 8405f61c..4da316bd 100644 --- a/core/integrations/traefik_test.go +++ b/core/integrations/traefik_test.go @@ -10,14 +10,14 @@ func TestTraefikIntegration(t *testing.T) { t.Run("List Services from Traefik", func(t *testing.T) { t.SkipNow() - services, err := traefikIntegrator.List() + services, err := TraefikIntegrator.List() require.Nil(t, err) assert.NotEqual(t, 0, len(services)) }) t.Run("Confirm Services from Traefik", func(t *testing.T) { t.SkipNow() - services, err := traefikIntegrator.List() + services, err := TraefikIntegrator.List() require.Nil(t, err) for _, s := range services { t.Log(s) diff --git a/core/notifier/events.go b/core/notifier/events.go index 7d72df08..445ccbb2 100644 --- a/core/notifier/events.go +++ b/core/notifier/events.go @@ -24,7 +24,7 @@ import ( // OnSave will trigger a notifier when it has been saved - Notifier interface func OnSave(method string) { for _, comm := range AllCommunications { - if isType(comm, new(Notifier)) { + if utils.IsType(comm, new(Notifier)) { notifier := comm.(Notifier) if notifier.Select().Method == method { notifier.OnSave() @@ -52,11 +52,11 @@ func OnFailure(s *types.Service, f *types.Failure) { sendMessages: for _, comm := range AllCommunications { - if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { + if utils.IsType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { notifier := comm.(Notifier).Select() log. WithField("trigger", "OnFailure"). - WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name)) + WithFields(utils.ToFields(notifier, s)).Debugln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name)) comm.(BasicEvents).OnFailure(s, f) } } @@ -74,11 +74,11 @@ func OnSuccess(s *types.Service) { } for _, comm := range AllCommunications { - if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) { + if utils.IsType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) { notifier := comm.(Notifier).Select() log. WithField("trigger", "OnSuccess"). - WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name)) + WithFields(utils.ToFields(notifier, s)).Debugln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name)) comm.(BasicEvents).OnSuccess(s) } } @@ -87,10 +87,10 @@ func OnSuccess(s *types.Service) { // OnNewService is triggered when a new service is created - ServiceEvents interface func OnNewService(s *types.Service) { for _, comm := range AllCommunications { - if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { + if utils.IsType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { log. WithField("trigger", "OnNewService"). - Infoln(fmt.Sprintf("Sending new service notification for service %v", s.Name)) + Debugln(fmt.Sprintf("Sending new service notification for service %v", s.Name)) comm.(ServiceEvents).OnNewService(s) } } @@ -102,8 +102,8 @@ func OnUpdatedService(s *types.Service) { return } for _, comm := range AllCommunications { - if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending updated service notification for service %v", s.Name)) + if utils.IsType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { + log.Debugln(fmt.Sprintf("Sending updated service notification for service %v", s.Name)) comm.(ServiceEvents).OnUpdatedService(s) } } @@ -115,8 +115,8 @@ func OnDeletedService(s *types.Service) { return } for _, comm := range AllCommunications { - if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name)) + if utils.IsType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { + log.Debugln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name)) comm.(ServiceEvents).OnDeletedService(s) } } @@ -125,8 +125,8 @@ func OnDeletedService(s *types.Service) { // OnNewUser is triggered when a new user is created - UserEvents interface func OnNewUser(u *types.User) { for _, comm := range AllCommunications { - if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending new user notification for user %v", u.Username)) + if utils.IsType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { + log.Debugln(fmt.Sprintf("Sending new user notification for user %v", u.Username)) comm.(UserEvents).OnNewUser(u) } } @@ -135,8 +135,8 @@ func OnNewUser(u *types.User) { // OnUpdatedUser is triggered when a new user is updated - UserEvents interface func OnUpdatedUser(u *types.User) { for _, comm := range AllCommunications { - if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending updated user notification for user %v", u.Username)) + if utils.IsType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { + log.Debugln(fmt.Sprintf("Sending updated user notification for user %v", u.Username)) comm.(UserEvents).OnUpdatedUser(u) } } @@ -145,8 +145,8 @@ func OnUpdatedUser(u *types.User) { // OnDeletedUser is triggered when a new user is deleted - UserEvents interface func OnDeletedUser(u *types.User) { for _, comm := range AllCommunications { - if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username)) + if utils.IsType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { + log.Debugln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username)) comm.(UserEvents).OnDeletedUser(u) } } @@ -155,8 +155,8 @@ func OnDeletedUser(u *types.User) { // OnUpdatedCore is triggered when the CoreApp settings are saved - CoreEvents interface func OnUpdatedCore(c *types.Core) { for _, comm := range AllCommunications { - if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending updated core notification")) + if utils.IsType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { + log.Debugln(fmt.Sprintf("Sending updated core notification")) comm.(CoreEvents).OnUpdatedCore(c) } } @@ -165,7 +165,7 @@ func OnUpdatedCore(c *types.Core) { // OnStart is triggered when the Statping service has started func OnStart(c *types.Core) { for _, comm := range AllCommunications { - if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { + if utils.IsType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { comm.(CoreEvents).OnUpdatedCore(c) } } @@ -174,7 +174,7 @@ func OnStart(c *types.Core) { // OnNewNotifier is triggered when a new notifier is loaded func OnNewNotifier(n *Notification) { for _, comm := range AllCommunications { - if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { + if utils.IsType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { comm.(NotifierEvents).OnNewNotifier(n) } } @@ -183,7 +183,7 @@ func OnNewNotifier(n *Notification) { // OnUpdatedNotifier is triggered when a notifier has been updated func OnUpdatedNotifier(n *Notification) { for _, comm := range AllCommunications { - if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { + if utils.IsType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { log.Infoln(fmt.Sprintf("Sending updated notifier for %v", n.Id)) comm.(NotifierEvents).OnUpdatedNotifier(n) } diff --git a/core/notifier/notifiers.go b/core/notifier/notifiers.go index e5b68ee7..0f5ed556 100644 --- a/core/notifier/notifiers.go +++ b/core/notifier/notifiers.go @@ -102,7 +102,7 @@ func (n *Notification) AfterFind() (err error) { func (n *Notification) AddQueue(uid string, msg interface{}) { data := &QueueData{uid, msg} n.Queue = append(n.Queue, data) - log.WithFields(utils.ToFields(data, n)).Infoln(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) + log.WithFields(utils.ToFields(data, n)).Debug(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) } // CanTest returns true if the notifier implements the OnTest interface @@ -129,7 +129,7 @@ func asNotification(n Notifier) *Notification { // AddNotifier accept a Notifier interface to be added into the array func AddNotifiers(notifiers ...Notifier) error { for _, n := range notifiers { - if isType(n, new(Notifier)) { + if utils.IsType(n, new(Notifier)) { err := checkNotifierForm(n) if err != nil { return err @@ -252,7 +252,7 @@ func Init(n Notifier) (*Notification, error) { if notify.Delay.Seconds() == 0 { notify.Delay = time.Duration(1 * time.Second) } - notify.testable = isType(n, new(Tester)) + notify.testable = utils.IsType(n, new(Tester)) notify.Form = n.Select().Form } return notify, err @@ -261,7 +261,7 @@ func Init(n Notifier) (*Notification, error) { // startAllNotifiers will start the go routine for each loaded notifier func startAllNotifiers() { for _, comm := range AllCommunications { - if isType(comm, new(Notifier)) { + if utils.IsType(comm, new(Notifier)) { notify := comm.(Notifier) if notify.Select().Enabled.Bool { notify.Select().close() @@ -290,9 +290,9 @@ CheckNotifier: msg := notification.Queue[0] err := n.Send(msg.Data) if err != nil { - log.WithFields(utils.ToFields(notification, msg)).Warnln(fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err)) + log.WithFields(utils.ToFields(notification, msg)).Error(fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err)) } else { - log.WithFields(utils.ToFields(notification, msg)).Infoln(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue))) + log.WithFields(utils.ToFields(notification, msg)).Debug(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue))) } notification.makeLog(msg.Data) if len(notification.Queue) > 1 { @@ -386,13 +386,6 @@ func (n *Notification) GetValue(dbField string) string { return "" } -// isType will return true if a variable can implement an interface -func isType(n interface{}, obj interface{}) bool { - one := reflect.TypeOf(n) - two := reflect.ValueOf(obj).Elem() - return one.Implements(two.Type()) -} - // isEnabled returns true if the notifier is enabled func isEnabled(n interface{}) bool { notifier := n.(Notifier).Select() diff --git a/core/sample.go b/core/sample.go index 8d6bd8e0..04580a8e 100644 --- a/core/sample.go +++ b/core/sample.go @@ -548,6 +548,10 @@ func TmpRecords(dbFile string) error { 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 := CoreApp.SelectAllServices(false); err != nil { return err @@ -555,6 +559,9 @@ func TmpRecords(dbFile string) error { if err := AttachNotifiers(); err != nil { return err } + if err := AddIntegrations(); err != nil { + return err + } CoreApp.Notifications = notifier.AllCommunications return nil } @@ -584,6 +591,10 @@ func TmpRecords(dbFile string) error { 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 := CoreApp.SelectAllServices(false); err != nil { return err diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d281594c..a1aa4081 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,5 +1,5 @@