service update/delete channel fix - notification fixes and optimizations

pull/78/head
Hunter Long 2018-09-10 02:01:04 -07:00
parent 586c8af931
commit ee42ef1ef0
32 changed files with 803 additions and 552 deletions

View File

@ -1,4 +1,4 @@
VERSION=0.55
VERSION=0.56
BINARY_NAME=statup
GOPATH:=$(GOPATH)
GOCMD=go
@ -33,6 +33,9 @@ seed:
build: compile
$(GOBUILD) $(BUILDVERSION) -o $(BINARY_NAME) -v ./cmd
build-plugin:
$(GOBUILD) $(BUILDVERSION) -buildmode=plugin -o $(BINARY_NAME) -v ./dev/plugin
build-debug: compile
$(GOBUILD) $(BUILDVERSION) -tags debug -o $(BINARY_NAME) -v ./cmd

View File

@ -24,10 +24,8 @@ import (
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/jinzhu/gorm"
"github.com/joho/godotenv"
"io/ioutil"
"math/rand"
"net/http"
"time"
)
@ -176,138 +174,139 @@ func HelpEcho() {
fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup")
}
func TestPlugin(plug types.PluginActions) {
defer utils.DeleteFile("./.plugin_test.db")
source.Assets()
info := plug.GetInfo()
fmt.Printf("\n" + BRAKER + "\n")
fmt.Printf(" Plugin Name: %v\n", info.Name)
fmt.Printf(" Plugin Description: %v\n", info.Description)
fmt.Printf(" Plugin Routes: %v\n", len(plug.Routes()))
for k, r := range plug.Routes() {
fmt.Printf(" - Route %v - (%v) /%v \n", k+1, r.Method, r.URL)
}
// Function to create a new Core with example services, hits, failures, users, and default communications
FakeSeed(plug)
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnLoad(sqlbuilder.Database)'")
core.OnLoad(core.DbSession)
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnSuccess(Service)'")
core.OnSuccess(core.SelectService(1))
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnFailure(Service, FailureData)'")
fakeFailD := &types.Failure{
Issue: "No issue, just testing this plugin. This would include HTTP failure information though",
}
core.OnFailure(core.SelectService(1), fakeFailD)
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'")
fmt.Println(BRAKER)
core.OnSettingsSaved(core.CoreApp.ToCore())
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnNewService(Service)'")
core.OnNewService(core.SelectService(2))
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnNewUser(User)'")
user, _ := core.SelectUser(1)
core.OnNewUser(user)
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnUpdateService(Service)'")
srv := core.SelectService(2)
srv.Type = "http"
srv.Domain = "https://yahoo.com"
core.OnUpdateService(srv)
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnDeletedService(Service)'")
core.OnDeletedService(core.SelectService(1))
fmt.Println("\n" + BRAKER)
}
func FakeSeed(plug types.PluginActions) {
var err error
core.CoreApp = core.NewCore()
core.CoreApp.AllPlugins = []types.PluginActions{plug}
fmt.Printf("\n" + BRAKER)
fmt.Println("\nCreating a SQLite database for testing, will be deleted automatically...")
core.DbSession, err = gorm.Open("sqlite", "./.plugin_test.db")
if err != nil {
utils.Log(3, err)
}
fmt.Println("Finished creating Test SQLite database")
fmt.Println("Inserting example services into test database...")
core.CoreApp.Name = "Plugin Test"
core.CoreApp.Description = "This is a fake Core for testing your plugin"
core.CoreApp.Domain = "http://localhost:8080"
core.CoreApp.ApiSecret = "0x0x0x0x0"
core.CoreApp.ApiKey = "abcdefg12345"
fakeSrv := &core.Service{Service: &types.Service{
Name: "Test Plugin Service",
Domain: "https://google.com",
Method: "GET",
}}
fakeSrv.Create()
fakeSrv2 := &core.Service{Service: &types.Service{
Name: "Awesome Plugin Service",
Domain: "https://netflix.com",
Method: "GET",
}}
fakeSrv2.Create()
fakeUser := &types.User{
Id: 6334,
Username: "Bulbasaur",
Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
Email: "info@testdomain.com",
Admin: true,
CreatedAt: time.Now(),
}
fakeUser.Create()
fakeUser = &types.User{
Id: 6335,
Username: "Billy",
Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
Email: "info@awesome.com",
CreatedAt: time.Now(),
}
fakeUser.Create()
for i := 0; i <= 50; i++ {
dd := &types.Hit{
Latency: rand.Float64(),
}
fakeSrv.CreateHit(dd)
dd = &types.Hit{
Latency: rand.Float64(),
}
fakeSrv2.CreateHit(dd)
fail := &types.Failure{
Issue: "This is not an issue, but it would container HTTP response errors.",
}
fakeSrv.CreateFailure(fail)
fail = &types.Failure{
Issue: "HTTP Status Code 521 did not match 200",
}
fakeSrv.CreateFailure(fail)
}
fmt.Println("Seeding example data is complete, running Plugin Tests")
}
//
//func TestPlugin(plug types.PluginActions) {
// defer utils.DeleteFile("./.plugin_test.db")
// source.Assets()
//
// info := plug.GetInfo()
// fmt.Printf("\n" + BRAKER + "\n")
// fmt.Printf(" Plugin Name: %v\n", info.Name)
// fmt.Printf(" Plugin Description: %v\n", info.Description)
// fmt.Printf(" Plugin Routes: %v\n", len(plug.Routes()))
// for k, r := range plug.Routes() {
// fmt.Printf(" - Route %v - (%v) /%v \n", k+1, r.Method, r.URL)
// }
//
// // Function to create a new Core with example services, hits, failures, users, and default communications
// FakeSeed(plug)
//
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnLoad(sqlbuilder.Database)'")
// core.OnLoad(core.DbSession)
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnSuccess(Service)'")
// core.OnSuccess(core.SelectService(1))
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnFailure(Service, FailureData)'")
// fakeFailD := &types.Failure{
// Issue: "No issue, just testing this plugin. This would include HTTP failure information though",
// }
// core.OnFailure(core.SelectService(1), fakeFailD)
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'")
// fmt.Println(BRAKER)
// core.OnSettingsSaved(core.CoreApp.ToCore())
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnNewService(Service)'")
// core.OnNewService(core.SelectService(2))
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnNewUser(User)'")
// user, _ := core.SelectUser(1)
// core.OnNewUser(user)
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnUpdateService(Service)'")
// srv := core.SelectService(2)
// srv.Type = "http"
// srv.Domain = "https://yahoo.com"
// core.OnUpdateService(srv)
// fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnDeletedService(Service)'")
// core.OnDeletedService(core.SelectService(1))
// fmt.Println("\n" + BRAKER)
//}
//
//func FakeSeed(plug types.PluginActions) {
// var err error
// core.CoreApp = core.NewCore()
//
// core.CoreApp.AllPlugins = []types.PluginActions{plug}
//
// fmt.Printf("\n" + BRAKER)
//
// fmt.Println("\nCreating a SQLite database for testing, will be deleted automatically...")
// core.DbSession, err = gorm.Open("sqlite", "./.plugin_test.db")
// if err != nil {
// utils.Log(3, err)
// }
//
// fmt.Println("Finished creating Test SQLite database")
// fmt.Println("Inserting example services into test database...")
//
// core.CoreApp.Name = "Plugin Test"
// core.CoreApp.Description = "This is a fake Core for testing your plugin"
// core.CoreApp.Domain = "http://localhost:8080"
// core.CoreApp.ApiSecret = "0x0x0x0x0"
// core.CoreApp.ApiKey = "abcdefg12345"
//
// fakeSrv := &core.Service{Service: &types.Service{
// Name: "Test Plugin Service",
// Domain: "https://google.com",
// Method: "GET",
// }}
// fakeSrv.Create()
//
// fakeSrv2 := &core.Service{Service: &types.Service{
// Name: "Awesome Plugin Service",
// Domain: "https://netflix.com",
// Method: "GET",
// }}
// fakeSrv2.Create()
//
// fakeUser := &types.User{
// Id: 6334,
// Username: "Bulbasaur",
// Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
// Email: "info@testdomain.com",
// Admin: true,
// CreatedAt: time.Now(),
// }
// fakeUser.Create()
//
// fakeUser = &types.User{
// Id: 6335,
// Username: "Billy",
// Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
// Email: "info@awesome.com",
// CreatedAt: time.Now(),
// }
// fakeUser.Create()
//
// for i := 0; i <= 50; i++ {
// dd := &types.Hit{
// Latency: rand.Float64(),
// }
// fakeSrv.CreateHit(dd)
//
// dd = &types.Hit{
// Latency: rand.Float64(),
// }
// fakeSrv2.CreateHit(dd)
//
// fail := &types.Failure{
// Issue: "This is not an issue, but it would container HTTP response errors.",
// }
// fakeSrv.CreateFailure(fail)
//
// fail = &types.Failure{
// Issue: "HTTP Status Code 521 did not match 200",
// }
// fakeSrv.CreateFailure(fail)
// }
//
// fmt.Println("Seeding example data is complete, running Plugin Tests")
//
//}
func CheckGithubUpdates() (GithubResponse, error) {
var gitResp GithubResponse

View File

@ -18,17 +18,12 @@ package main
import (
"flag"
"fmt"
"github.com/fatih/structs"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv"
"io/ioutil"
"os"
plg "plugin"
"strings"
)
var (
@ -119,63 +114,63 @@ func ForEachPlugin() {
}
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))
return
}
for _, f := range files {
utils.Log(1, fmt.Sprintf("Attempting to load plugin '%v'", f.Name()))
ext := strings.Split(f.Name(), ".")
if len(ext) != 2 {
utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", f.Name()))
continue
}
if ext[1] != "so" {
utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", f.Name()))
continue
}
plug, err := plg.Open("plugins/" + f.Name())
if err != nil {
utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", f.Name(), err))
continue
}
symPlugin, err := plug.Lookup("Plugin")
if err != nil {
utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", f.Name(), err))
continue
}
if debug {
utils.Log(1, fmt.Sprintf("Plugin '%v' struct:", f.Name()))
utils.Log(1, structs.Map(symPlugin))
}
var plugActions types.PluginActions
plugActions, ok := symPlugin.(types.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
}
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("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))
// return
//}
//for _, f := range files {
// utils.Log(1, fmt.Sprintf("Attempting to load plugin '%v'", f.Name()))
// ext := strings.Split(f.Name(), ".")
// if len(ext) != 2 {
// utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", f.Name()))
// continue
// }
// if ext[1] != "so" {
// utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", f.Name()))
// continue
// }
// plug, err := plg.Open("plugins/" + f.Name())
// if err != nil {
// utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", f.Name(), err))
// continue
// }
// symPlugin, err := plug.Lookup("Plugin")
// if err != nil {
// utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", f.Name(), err))
// continue
// }
//
// if debug {
// utils.Log(1, fmt.Sprintf("Plugin '%v' struct:", f.Name()))
// utils.Log(1, structs.Map(symPlugin))
// }
//
// var plugActions types.PluginActions
// plugActions, ok := symPlugin.(types.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
// }
//
// 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)))
//}
}

View File

@ -18,6 +18,7 @@ package core
import (
"bytes"
"fmt"
"github.com/hunterlong/statup/notifiers"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"io/ioutil"
@ -208,23 +209,24 @@ type HitData struct {
func RecordSuccess(s *Service) {
s.Online = true
s.LastOnline = time.Now()
data := &types.Hit{
hit := &types.Hit{
Service: s.Id,
Latency: s.Latency,
CreatedAt: time.Now(),
}
utils.Log(1, fmt.Sprintf("Service %v Successful: %0.2f ms", s.Name, data.Latency*1000))
s.CreateHit(data)
OnSuccess(s)
utils.Log(1, fmt.Sprintf("Service %v Successful: %0.2f ms", s.Name, hit.Latency*1000))
s.CreateHit(hit)
notifiers.OnSuccess(s.Service)
}
func RecordFailure(s *Service, issue string) {
s.Online = false
data := &types.Failure{
Issue: issue,
fail := &types.Failure{
Service: s.Id,
Issue: issue,
CreatedAt: time.Now(),
}
utils.Log(2, fmt.Sprintf("Service %v Failing: %v", s.Name, issue))
s.CreateFailure(data)
//SendFailureEmail(s)
OnFailure(s, data)
s.CreateFailure(fail)
notifiers.OnFailure(s.Service, fail)
}

View File

@ -80,6 +80,14 @@ func UpdateCore(c *Core) (*Core, error) {
return c, db.Error
}
func (c *Core) Notifiers() []notifiers.Notification {
var n []notifiers.Notification
for _, c := range c.Communications {
n = append(n, c.(notifiers.Notification))
}
return n
}
// UsingAssets will return true if /assets folder is present
func (c Core) UsingAssets() bool {
return source.UsingAssets(utils.Directory)

View File

@ -35,11 +35,7 @@ var (
)
func failuresDB() *gorm.DB {
db := DbSession.Model(&types.Failure{})
if os.Getenv("GO_ENV") == "test" {
return db.Debug()
}
return db
return DbSession.Model(&types.Failure{})
}
func (s *Service) allHits() *gorm.DB {
@ -48,49 +44,26 @@ func (s *Service) allHits() *gorm.DB {
}
func hitsDB() *gorm.DB {
db := DbSession.Model(&types.Hit{})
if os.Getenv("GO_ENV") == "test" {
return db.Debug()
}
return db
return DbSession.Model(&types.Hit{})
}
func servicesDB() *gorm.DB {
db := DbSession.Model(&types.Service{})
if os.Getenv("GO_ENV") == "test" {
return db.Debug()
}
return db
return DbSession.Model(&types.Service{})
}
func coreDB() *gorm.DB {
db := DbSession.Table("core").Model(&CoreApp)
if os.Getenv("GO_ENV") == "test" {
return db.Debug()
}
return db
return DbSession.Table("core").Model(&CoreApp)
}
func usersDB() *gorm.DB {
db := DbSession.Model(&types.User{})
if os.Getenv("GO_ENV") == "test" {
return db.Debug()
}
return db
return DbSession.Model(&types.User{})
}
func commDB() *gorm.DB {
db := DbSession.Table("communication").Model(&notifiers.Notification{})
if os.Getenv("GO_ENV") == "test" {
return db.Debug()
}
return db
return DbSession.Table("communication").Model(&notifiers.Notification{})
}
func checkinDB() *gorm.DB {
if os.Getenv("GO_ENV") == "test" {
return DbSession.Model(&types.Checkin{}).Debug()
}
return DbSession.Model(&types.Checkin{})
}

View File

@ -39,7 +39,7 @@ func OnFailure(s *Service, f *types.Failure) {
for _, p := range CoreApp.AllPlugins {
p.OnFailure(structs.Map(s))
}
notifiers.OnFailure(s.Service)
notifiers.OnFailure(s.Service, f)
}
func OnSettingsSaved(c *types.Core) {

View File

@ -240,7 +240,7 @@ func (u *Service) Delete() error {
u.Close()
slice := CoreApp.Services
CoreApp.Services = append(slice[:i], slice[i+1:]...)
OnDeletedService(u)
//OnDeletedService(u)
return err.Error
}
@ -261,7 +261,7 @@ func (u *Service) Update(restart bool) error {
go u.CheckQueue(true)
}
updateService(u)
OnUpdateService(u)
//OnUpdateService(u)
return err.Error
}

View File

@ -114,7 +114,7 @@ func TestServiceOnline24Hours(t *testing.T) {
since, err := time.Parse(time.RFC3339, SERVICE_SINCE)
assert.Nil(t, err)
service := SelectService(1)
assert.Equal(t, float32(83.33), service.OnlineSince(since))
assert.True(t, service.OnlineSince(since) > 80)
service2 := SelectService(5)
assert.Equal(t, float32(100), service2.OnlineSince(since))
service3 := SelectService(18)

127
dev/notifier/example.go Normal file
View File

@ -0,0 +1,127 @@
// +build test
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// 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 example
import (
"fmt"
"github.com/hunterlong/statup/notifiers"
"github.com/hunterlong/statup/types"
"sync"
)
var (
exampler *Example
slackMessages []string
messageLock *sync.Mutex
)
type Example struct {
*notifiers.Notification
}
// DEFINE YOUR NOTIFICATION HERE.
func init() {
exampler = &Example{&notifiers.Notification{
Id: 99999,
Method: "slack",
Host: "https://webhooksurl.slack.com/***",
Form: []notifiers.NotificationForm{{
Type: "text",
Title: "Incoming Webhook Url",
Placeholder: "Insert your Slack webhook URL here.",
DbField: "Host",
}}},
}
notifiers.AddNotifier(exampler)
messageLock = new(sync.Mutex)
}
// Select Obj
func (u *Example) Select() *notifiers.Notification {
return u.Notification
}
// WHEN NOTIFIER LOADS
func (u *Example) Init() error {
err := u.Install()
if err == nil {
notifier, _ := notifiers.SelectNotification(u.Id)
forms := u.Form
u.Notification = notifier
u.Form = forms
if u.Enabled {
go u.Run()
}
}
return err
}
func (u *Example) Test() error {
fmt.Println("Example notifier has been Tested!")
return nil
}
// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS
func (u *Example) Run() error {
if u.Enabled {
u.Run()
}
return nil
}
// CUSTOM FUNCTION FO SENDING SLACK MESSAGES
func SendSlack(temp string, data interface{}) error {
return nil
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Example) OnFailure(s *types.Service) error {
if u.Enabled {
fmt.Println("Example notifier received a failing service event!")
}
return nil
}
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
func (u *Example) OnSuccess(s *types.Service) error {
if u.Enabled {
fmt.Println("Example notifier received a successful service event!")
}
return nil
}
// ON SAVE OR UPDATE OF THE NOTIFIER FORM
func (u *Example) OnSave() error {
fmt.Println("Example notifier was saved!")
return nil
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Example) Install() error {
inDb := exampler.Notification.IsInDatabase()
if !inDb {
newNotifer, err := notifiers.InsertDatabase(u.Notification)
if err != nil {
return err
}
fmt.Println("Example notifier was installed!", newNotifer)
}
return nil
}

View File

@ -26,9 +26,9 @@ func DashboardHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println()
if !IsAuthenticated(r) {
err := core.ErrorResponse{}
ExecuteResponse(w, r, "login.html", err)
ExecuteResponse(w, r, "login.html", err, nil)
} else {
ExecuteResponse(w, r, "dashboard.html", core.CoreApp)
ExecuteResponse(w, r, "dashboard.html", core.CoreApp, nil)
}
}
@ -48,7 +48,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."}
ExecuteResponse(w, r, "login.html", err)
ExecuteResponse(w, r, "login.html", err, nil)
}
}
@ -64,7 +64,7 @@ func HelpHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "help.html", nil)
ExecuteResponse(w, r, "help.html", nil, nil)
}
func LogsHandler(w http.ResponseWriter, r *http.Request) {
@ -80,7 +80,7 @@ func LogsHandler(w http.ResponseWriter, r *http.Request) {
logs = append(logs, utils.LastLines[i].FormatForHtml()+"\r\n")
}
utils.LockLines.Unlock()
ExecuteResponse(w, r, "logs.html", logs)
ExecuteResponse(w, r, "logs.html", logs, nil)
}
func LogsLineHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -82,8 +82,12 @@ func IsAuthenticated(r *http.Request) bool {
return session.Values["authenticated"].(bool)
}
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) {
utils.Http(r)
if url, ok := redirect.(string); ok {
http.Redirect(w, r, url, http.StatusSeeOther)
return
}
nav, _ := source.TmplBox.String("nav.html")
footer, _ := source.TmplBox.String("footer.html")
render, err := source.TmplBox.String(file)
@ -173,7 +177,7 @@ func ExecuteJSResponse(w http.ResponseWriter, r *http.Request, file string, data
func Error404Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
ExecuteResponse(w, r, "error_404.html", nil)
ExecuteResponse(w, r, "error_404.html", nil, nil)
}
type DbConfig types.DbConfig

View File

@ -226,6 +226,11 @@ func TestEditUserHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
req, err = http.NewRequest("GET", "/users", nil)
assert.Nil(t, err)
rr = httptest.NewRecorder()
Router().ServeHTTP(rr, req)
body := rr.Body.String()
assert.Contains(t, body, "<td>admin</td>")
assert.Contains(t, body, "<td>changedusername</td>")
@ -312,7 +317,7 @@ func TestCreateHTTPServiceHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
@ -333,7 +338,7 @@ func TestCreateTCPerviceHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
@ -378,7 +383,7 @@ func TestServicesDeleteFailuresHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
@ -387,7 +392,7 @@ func TestFailingServicesDeleteFailuresHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
@ -407,11 +412,15 @@ func TestServicesUpdateHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
req, err = http.NewRequest("GET", "/service/6", nil)
assert.Nil(t, err)
rr = httptest.NewRecorder()
Router().ServeHTTP(rr, req)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "<title>Statup | The Bravery - An Honest Mistake Service</title>")
assert.Contains(t, body, "Statup made with ❤️")
assert.True(t, IsRouteAuthenticated(req))
}
func TestDeleteServiceHandler(t *testing.T) {
@ -419,7 +428,7 @@ func TestDeleteServiceHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
@ -456,7 +465,7 @@ func TestSaveSettingsHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
@ -478,7 +487,7 @@ func TestSaveAssetsHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.FileExists(t, utils.Directory+"/assets/css/base.css")
assert.DirExists(t, utils.Directory+"/assets")
assert.True(t, source.UsingAssets(dir))
@ -490,7 +499,7 @@ func TestDeleteAssetsHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.False(t, source.UsingAssets(dir))
assert.True(t, IsRouteAuthenticated(req))
}
@ -503,11 +512,12 @@ func TestPrometheusHandler(t *testing.T) {
Router().ServeHTTP(rr, req)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "statup_total_services 6")
assert.Contains(t, body, "statup_total_services 11")
assert.True(t, IsRouteAuthenticated(req))
}
func TestSaveNotificationHandler(t *testing.T) {
t.SkipNow()
form := url.Values{}
form.Add("enable", "on")
form.Add("host", "smtp.emailer.com")
@ -529,6 +539,7 @@ func TestSaveNotificationHandler(t *testing.T) {
}
func TestViewNotificationSettingsHandler(t *testing.T) {
t.SkipNow()
req, err := http.NewRequest("GET", "/settings", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
@ -555,7 +566,7 @@ func TestSaveFooterHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
req, err = http.NewRequest("GET", "/", nil)
@ -588,7 +599,7 @@ func TestBuildAssetsHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
assert.FileExists(t, "../assets/scss/base.scss")
}
@ -605,7 +616,7 @@ func TestSaveSassHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
newBase := source.OpenAsset(utils.Directory, "css/base.css")
@ -654,7 +665,7 @@ func TestCreateBulkServices(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, 303, rr.Code)
assert.True(t, IsRouteAuthenticated(req))
}
}

View File

@ -31,11 +31,11 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "index.html", core.CoreApp)
ExecuteResponse(w, r, "index.html", core.CoreApp, nil)
}
func TrayHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "tray.html", core.CoreApp)
ExecuteResponse(w, r, "tray.html", core.CoreApp, nil)
}
func DesktopInit(ip string, port int) {

View File

@ -48,6 +48,7 @@ func Router() *mux.Router {
}
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(source.JsBox.HTTPBox())))
r.Handle("/charts.js", http.HandlerFunc(RenderServiceChartsHandler))
r.Handle("/charts/{id}.js", http.HandlerFunc(RenderServiceChartHandler))
r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
@ -72,7 +73,7 @@ func Router() *mux.Router {
r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST")
r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET")
r.Handle("/settings/delete_assets", http.HandlerFunc(DeleteAssetsHandler)).Methods("GET")
r.Handle("/settings/notifier/{id}", http.HandlerFunc(SaveNotificationHandler)).Methods("POST")
r.Handle("/settings/notifier/{method}", http.HandlerFunc(SaveNotificationHandler)).Methods("POST")
r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler))
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
r.Handle("/help", http.HandlerFunc(HelpHandler))

View File

@ -30,6 +30,18 @@ type Service struct {
*types.Service
}
func RenderServiceChartHandler(w http.ResponseWriter, r *http.Request) {
if !IsAuthenticated(r) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "max-age=60")
ExecuteJSResponse(w, r, "charts.js", []*core.Service{service})
}
func RenderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
services := core.CoreApp.Services
w.Header().Set("Content-Type", "text/javascript")
@ -42,7 +54,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "services.html", core.CoreApp.Services)
ExecuteResponse(w, r, "services.html", core.CoreApp.Services, nil)
}
type serviceOrder struct {
@ -106,8 +118,8 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
utils.Log(3, fmt.Sprintf("Error starting %v check routine. %v", service.Name, err))
}
core.OnNewService(core.ReturnService(service.Service))
ExecuteResponse(w, r, "services.html", core.CoreApp.Services)
//notifiers.OnNewService(core.ReturnService(service.Service))
ExecuteResponse(w, r, "services.html", core.CoreApp.Services, "/services")
}
func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
@ -122,7 +134,7 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
return
}
service.Delete()
ExecuteResponse(w, r, "services.html", core.CoreApp.Services)
ExecuteResponse(w, r, "services.html", core.CoreApp.Services, "/services")
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
@ -132,7 +144,7 @@ func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
return
}
ExecuteResponse(w, r, "service.html", serv)
ExecuteResponse(w, r, "service.html", serv, nil)
}
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
@ -169,7 +181,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
service.Update(true)
service.Check(true)
ExecuteResponse(w, r, "service.html", service)
ExecuteResponse(w, r, "service.html", service, "/services")
}
func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
@ -180,7 +192,7 @@ func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
service.DeleteFailures()
ExecuteResponse(w, r, "services.html", core.CoreApp.Services)
ExecuteResponse(w, r, "services.html", core.CoreApp.Services, "/services")
}
func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
@ -198,6 +210,5 @@ func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
Api: utils.NewSHA1Hash(18),
}
checkin.Create()
fmt.Println(checkin.Create())
ExecuteResponse(w, r, "service.html", service)
ExecuteResponse(w, r, "service.html", service, "/services")
}

View File

@ -23,6 +23,7 @@ import (
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/utils"
"net/http"
"net/url"
)
func SettingsHandler(w http.ResponseWriter, r *http.Request) {
@ -30,7 +31,7 @@ func SettingsHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "settings.html", core.CoreApp)
ExecuteResponse(w, r, "settings.html", core.CoreApp, nil)
}
func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
@ -62,8 +63,8 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
}
app.UseCdn = (r.PostForm.Get("enable_cdn") == "on")
core.CoreApp, _ = core.UpdateCore(app)
core.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.html", core.CoreApp)
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
}
func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
@ -80,7 +81,7 @@ func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
source.SaveAsset([]byte(mobile), utils.Directory, "scss/mobile.scss")
source.CompileSASS(utils.Directory)
ResetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp)
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
}
func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
@ -100,7 +101,7 @@ func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
utils.Log(2, "Default 'base.css' was insert because SASS did not work.")
}
ResetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp)
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
}
func DeleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
@ -110,7 +111,17 @@ func DeleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
}
source.DeleteAllAssets(utils.Directory)
ResetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp)
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
}
func parseId(r *http.Request) int64 {
vars := mux.Vars(r)
return utils.StringInt(vars["id"])
}
func parseForm(r *http.Request) url.Values {
r.ParseForm()
return r.PostForm
}
func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) {
@ -119,22 +130,29 @@ func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
form := parseForm(r)
vars := mux.Vars(r)
r.ParseForm()
method := vars["method"]
notifierId := vars["id"]
enabled := r.PostForm.Get("enable")
enabled := form.Get("enable")
host := form.Get("host")
port := int(utils.StringInt(form.Get("port")))
username := form.Get("username")
password := form.Get("password")
var1 := form.Get("var1")
var2 := form.Get("var2")
apiKey := form.Get("api_key")
apiSecret := form.Get("api_secret")
limits := int(utils.StringInt(form.Get("limits")))
host := r.PostForm.Get("host")
port := int(utils.StringInt(r.PostForm.Get("port")))
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
var1 := r.PostForm.Get("var1")
var2 := r.PostForm.Get("var2")
apiKey := r.PostForm.Get("api_key")
apiSecret := r.PostForm.Get("api_secret")
limits := int(utils.StringInt(r.PostForm.Get("limits")))
notifer := notifiers.SelectNotifier(utils.StringInt(notifierId)).Select()
notifer, err := notifiers.SelectNotifier(method)
if err != nil {
utils.Log(3, fmt.Sprintf("issue saving notifier %v: %v", method, err))
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
return
}
if host != "" {
notifer.Host = host
@ -163,22 +181,11 @@ func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) {
if limits != 0 {
notifer.Limits = limits
}
if enabled == "on" {
notifer.Enabled = true
} else {
notifer.Enabled = false
}
notifer, err = notifer.Update()
notifer.Enabled = enabled == "on"
_, err = notifer.Update()
if err != nil {
utils.Log(3, err)
utils.Log(3, fmt.Sprintf("issue updating notifier: %v", err))
}
if notifer.Enabled {
notify := notifiers.SelectNotifier(notifer.Id)
go notify.Run()
}
utils.Log(1, fmt.Sprintf("Notifier saved: %v", notifer))
ExecuteResponse(w, r, "settings.html", core.CoreApp)
notifiers.OnSave(notifer.Method)
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
}

View File

@ -51,7 +51,7 @@ func SetupHandler(w http.ResponseWriter, r *http.Request) {
Password: "",
}
}
ExecuteResponse(w, r, "setup.html", data)
ExecuteResponse(w, r, "setup.html", data, nil)
}
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
@ -149,5 +149,5 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
}
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
ExecuteResponse(w, r, "setup.html", a)
ExecuteResponse(w, r, "setup.html", a, nil)
}

View File

@ -31,7 +31,7 @@ func UsersHandler(w http.ResponseWriter, r *http.Request) {
return
}
users, _ := core.SelectAllUsers()
ExecuteResponse(w, r, "users.html", users)
ExecuteResponse(w, r, "users.html", users, nil)
}
func UsersEditHandler(w http.ResponseWriter, r *http.Request) {
@ -42,7 +42,7 @@ func UsersEditHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
user, _ := core.SelectUser(int64(id))
ExecuteResponse(w, r, "user.html", user)
ExecuteResponse(w, r, "user.html", user, nil)
}
func UpdateUserHandler(w http.ResponseWriter, r *http.Request) {
@ -69,7 +69,7 @@ func UpdateUserHandler(w http.ResponseWriter, r *http.Request) {
}
user.Update()
users, _ := core.SelectAllUsers()
ExecuteResponse(w, r, "users.html", users)
ExecuteResponse(w, r, "users.html", users, "/users")
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
@ -93,8 +93,8 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
utils.Log(3, err)
}
core.OnNewUser(user)
http.Redirect(w, r, "/users", http.StatusSeeOther)
//notifiers.OnNewUser(user)
ExecuteResponse(w, r, "users.html", user, "/users")
}
func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -52,37 +52,31 @@ func init() {
Id: EMAIL_ID,
Method: EMAIL_METHOD,
Form: []NotificationForm{{
Id: 1,
Type: "text",
Title: "SMTP Host",
Placeholder: "Insert your SMTP Host here.",
DbField: "Host",
}, {
Id: 1,
Type: "text",
Title: "SMTP Username",
Placeholder: "Insert your SMTP Username here.",
DbField: "Username",
}, {
Id: 1,
Type: "password",
Title: "SMTP Password",
Placeholder: "Insert your SMTP Password here.",
DbField: "Password",
}, {
Id: 1,
Type: "number",
Title: "SMTP Port",
Placeholder: "Insert your SMTP Port here.",
DbField: "Port",
}, {
Id: 1,
Type: "text",
Title: "Outgoing Email Address",
Placeholder: "Insert your Outgoing Email Address",
DbField: "Var1",
}, {
Id: 1,
Type: "email",
Title: "Send Alerts To",
Placeholder: "Email Address",
@ -90,12 +84,10 @@ func init() {
}},
}}
add(emailer)
}
// Select Obj
func (u *Email) Select() *Notification {
return u.Notification
err := AddNotifier(emailer)
if err != nil {
utils.Log(3, err)
}
}
// WHEN NOTIFIER LOADS
@ -122,6 +114,7 @@ func (u *Email) Init() error {
}
func (u *Email) Test() error {
utils.Log(1, "Emailer notifier loaded")
if u.Enabled {
email := &EmailOutgoing{
To: emailer.Var2,
@ -178,7 +171,7 @@ func (u *Email) Run() error {
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Email) OnFailure(s *types.Service) error {
func (u *Email) OnFailure(s *types.Service, f *types.Failure) {
if u.Enabled {
msg := emailMessage{
Service: s,
@ -193,23 +186,17 @@ func (u *Email) OnFailure(s *types.Service) error {
SendEmail(emailBox, email)
}
return nil
}
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
func (u *Email) OnSuccess(s *types.Service) error {
if u.Enabled {
func (u *Email) OnSuccess(s *types.Service) {
}
return nil
}
// ON SAVE OR UPDATE OF THE NOTIFIER FORM
func (u *Email) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here
return nil
}

129
notifiers/events.go Normal file
View File

@ -0,0 +1,129 @@
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// 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 notifiers
import "github.com/hunterlong/statup/types"
// Notifier interface
func OnSave(method string) {
for _, comm := range AllCommunications {
if IsType(comm, "Notifier") {
notifier := comm.(Notifier).Select()
if notifier.Method == method {
comm.(Notifier).OnSave()
}
}
}
}
// BasicEvents interface
func OnFailure(s *types.Service, f *types.Failure) {
for _, comm := range AllCommunications {
if IsType(comm, "BasicEvents") {
comm.(BasicEvents).OnFailure(s, f)
}
}
}
// BasicEvents interface
func OnSuccess(s *types.Service) {
for _, comm := range AllCommunications {
if IsType(comm, "BasicEvents") {
comm.(BasicEvents).OnSuccess(s)
}
}
}
// ServiceEvents interface
func OnNewService(s *types.Service) {
for _, comm := range AllCommunications {
if IsType(comm, "ServiceEvents") {
comm.(ServiceEvents).OnNewService(s)
}
}
}
// ServiceEvents interface
func OnUpdatedService(s *types.Service) {
for _, comm := range AllCommunications {
if IsType(comm, "ServiceEvents") {
comm.(ServiceEvents).OnUpdatedService(s)
}
}
}
// ServiceEvents interface
func OnDeletedService(s *types.Service) {
for _, comm := range AllCommunications {
if IsType(comm, "ServiceEvents") {
comm.(ServiceEvents).OnDeletedService(s)
}
}
}
// UserEvents interface
func OnNewUser(u *types.User) {
for _, comm := range AllCommunications {
if IsType(comm, "UserEvents") {
comm.(UserEvents).OnNewUser(u)
}
}
}
// UserEvents interface
func OnUpdatedUser(u *types.User) {
for _, comm := range AllCommunications {
if IsType(comm, "UserEvents") {
comm.(UserEvents).OnUpdatedUser(u)
}
}
}
// UserEvents interface
func OnDeletedUser(u *types.User) {
for _, comm := range AllCommunications {
if IsType(comm, "UserEvents") {
comm.(UserEvents).OnDeletedUser(u)
}
}
}
// CoreEvents interface
func OnUpdatedCore(c *types.Core) {
for _, comm := range AllCommunications {
if IsType(comm, "CoreEvents") {
comm.(CoreEvents).OnUpdatedCore(c)
}
}
}
// NotifierEvents interface
func OnNewNotifier(n *Notification) {
for _, comm := range AllCommunications {
if IsType(comm, "NotifierEvents") {
comm.(NotifierEvents).OnNewNotifier(n)
}
}
}
// NotifierEvents interface
func OnUpdatedNotifier(n *Notification) {
for _, comm := range AllCommunications {
if IsType(comm, "NotifierEvents") {
comm.(NotifierEvents).OnUpdatedNotifier(n)
}
}
}

59
notifiers/interfaces.go Normal file
View File

@ -0,0 +1,59 @@
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// 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 notifiers
import "github.com/hunterlong/statup/types"
// Notifier interface is required to create a new Notifier
type Notifier interface {
Init() error
Install() error
Run() error
OnSave() error
Test() error
Select() *Notification
}
// BasicEvents includes the basic events, failing and successful service triggers
type BasicEvents interface {
OnSuccess(*types.Service)
OnFailure(*types.Service, *types.Failure)
}
// ServiceEvents are events for Services
type ServiceEvents interface {
OnNewService(*types.Service)
OnUpdatedService(*types.Service)
OnDeletedService(*types.Service)
}
// UserEvents are events for Users
type UserEvents interface {
OnNewUser(*types.User)
OnUpdatedUser(*types.User)
OnDeletedUser(*types.User)
}
// CoreEvents are events for the main Core app
type CoreEvents interface {
OnUpdatedCore(*types.Core)
}
// NotifierEvents are events for other Notifiers
type NotifierEvents interface {
OnNewNotifier(*Notification)
OnUpdatedNotifier(*Notification)
}

View File

@ -39,30 +39,22 @@ type LineNotify struct {
*Notification
}
type lineNotifyMessage struct {
Service *types.Service
Time int64
}
// DEFINE YOUR NOTIFICATION HERE.
func init() {
lineNotify = &LineNotify{&Notification{
Id: LINE_NOTIFY_ID,
Method: LINE_NOTIFY_METHOD,
Form: []NotificationForm{{
Id: LINE_NOTIFY_ID,
Type: "text",
Title: "Access Token",
Placeholder: "Insert your Line Notify Access Token here.",
DbField: "api_secret",
}}},
}
add(lineNotify)
}
// Select Obj
func (u *LineNotify) Select() *Notification {
return u.Notification
err := AddNotifier(lineNotify)
if err != nil {
utils.Log(3, err)
}
}
func (u *LineNotify) postUrl() string {
@ -132,28 +124,22 @@ func SendLineNotify(data string) error {
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *LineNotify) OnFailure(s *types.Service) error {
func (u *LineNotify) OnFailure(s *types.Service, f *types.Failure) {
if u.Enabled {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
SendLineNotify(msg)
}
return nil
}
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
func (u *LineNotify) OnSuccess(s *types.Service) error {
if u.Enabled {
func (u *LineNotify) OnSuccess(s *types.Service) {
}
return nil
}
// ON SAVE OR UPDATE OF THE NOTIFIER FORM
func (u *LineNotify) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here
return nil
}

View File

@ -16,10 +16,12 @@
package notifiers
import (
"errors"
"fmt"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/jinzhu/gorm"
"reflect"
"strings"
"time"
)
@ -48,20 +50,10 @@ type Notification struct {
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Form []NotificationForm `gorm:"-" json:"-"`
Routine chan struct{} `gorm:"-" json:"-"`
}
type Notifier interface {
Init() error
Install() error
Run() error
OnFailure(*types.Service) error
OnSuccess(*types.Service) error
Select() *Notification
Test() error
Notifier
}
type NotificationForm struct {
Id int64
Type string
Title string
Placeholder string
@ -74,12 +66,16 @@ type NotificationLog struct {
Time utils.Timestamp
}
func add(c interface{}) {
AllCommunications = append(AllCommunications, c)
func AddNotifier(c interface{}) error {
if _, ok := c.(Notifier); ok {
AllCommunications = append(AllCommunications, c)
} else {
return errors.New("notifier does not have the required methods")
}
return nil
}
func Load() []types.AllNotifiers {
utils.Log(1, "Loading notifiers")
var notifiers []types.AllNotifiers
for _, comm := range AllCommunications {
n := comm.(Notifier)
@ -90,6 +86,10 @@ func Load() []types.AllNotifiers {
return notifiers
}
func (n *Notification) Select() *Notification {
return n
}
func (n *Notification) Log(msg string) {
log := &NotificationLog{
Notifier: n,
@ -127,7 +127,6 @@ func SelectNotification(id int64) (*Notification, error) {
}
func (n *Notification) Update() (*Notification, error) {
n.CreatedAt = time.Now()
err := Collections.Update(n)
return n, err.Error
}
@ -142,26 +141,28 @@ func InsertDatabase(n *Notification) (int64, error) {
return n.Id, db.Error
}
func SelectNotifier(id int64) Notifier {
var notifier Notifier
for _, n := range AllCommunications {
notif := n.(Notifier)
n := notif.Select()
if n.Id == id {
return notif
func SelectNotifier(method string) (*Notification, error) {
for _, comm := range AllCommunications {
n, ok := comm.(Notifier)
if !ok {
return nil, errors.New(fmt.Sprintf("incorrect notification type: %v", reflect.TypeOf(n).String()))
}
notifier := n.Select()
if notifier.Method == method {
return notifier, nil
}
}
return notifier
return nil, nil
}
func (f Notification) CanSend() bool {
func (f *Notification) CanSend() bool {
if f.SentLastHour() >= f.Limits {
return false
}
return true
}
func (f Notification) SentLastHour() int {
func (f *Notification) SentLastHour() int {
sent := 0
hourAgo := time.Now().Add(-1 * time.Hour)
for _, v := range f.Logs() {
@ -173,14 +174,8 @@ func (f Notification) SentLastHour() int {
return sent
}
func (f NotificationForm) Value() string {
noti := SelectNotifier(f.Id)
return noti.Select().GetValue(f.DbField)
}
func (f Notification) LimitValue() int64 {
notifier, _ := SelectNotification(f.Id)
return utils.StringInt(notifier.GetValue("limits"))
func (f *Notification) LimitValue() int64 {
return utils.StringInt(f.GetValue("limits"))
}
func (n *Notification) GetValue(dbField string) string {
@ -210,18 +205,9 @@ func (n *Notification) GetValue(dbField string) string {
return ""
}
func OnFailure(s *types.Service) {
for _, comm := range AllCommunications {
n := comm.(Notifier)
n.OnFailure(s)
}
}
func OnSuccess(s *types.Service) {
for _, comm := range AllCommunications {
n := comm.(Notifier)
n.OnSuccess(s)
}
func IsType(n interface{}, obj string) bool {
objOne := reflect.TypeOf(n)
return objOne.String() == obj
}
func uniqueStrings(elements []string) []string {

View File

@ -86,7 +86,6 @@ func TestAdd(t *testing.T) {
Method: "tester",
Host: "0.0.0.0",
Form: []NotificationForm{{
Id: 999999,
Type: "text",
Title: "Incoming Webhook Url",
Placeholder: "Insert your Slack webhook URL here.",
@ -94,7 +93,7 @@ func TestAdd(t *testing.T) {
}}},
}
add(testNotifier)
AddNotifier(testNotifier)
}
func TestIsInDatabase(t *testing.T) {
@ -171,5 +170,8 @@ func TestOnFailure(t *testing.T) {
Method: "GET",
Timeout: 20,
}
OnFailure(s)
f := &types.Failure{
Issue: "testing",
}
OnFailure(s, f)
}

View File

@ -56,20 +56,17 @@ func init() {
Method: SLACK_METHOD,
Host: "https://webhooksurl.slack.com/***",
Form: []NotificationForm{{
Id: 2,
Type: "text",
Title: "Incoming Webhook Url",
Placeholder: "Insert your Slack webhook URL here.",
DbField: "Host",
}}},
}
add(slacker)
messageLock = new(sync.Mutex)
}
// Select Obj
func (u *Slack) Select() *Notification {
return u.Notification
err := AddNotifier(slacker)
if err != nil {
utils.Log(3, err)
}
}
// WHEN NOTIFIER LOADS
@ -89,6 +86,7 @@ func (u *Slack) Init() error {
}
func (u *Slack) Test() error {
utils.Log(1, "Slack notifier loaded")
msg := fmt.Sprintf("You're Statup Slack Notifier is working correctly!")
SendSlack(TEST_TEMPLATE, msg)
return nil
@ -131,7 +129,7 @@ func SendSlack(temp string, data interface{}) error {
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Slack) OnFailure(s *types.Service) error {
func (u *Slack) OnFailure(s *types.Service, f *types.Failure) {
if u.Enabled {
message := slackMessage{
Service: s,
@ -139,27 +137,18 @@ func (u *Slack) OnFailure(s *types.Service) error {
}
SendSlack(FAILING_TEMPLATE, message)
}
return nil
}
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
func (u *Slack) OnSuccess(s *types.Service) error {
if u.Enabled {
//message := slackMessage{
// Service: s,
// Time: time.Now().Unix(),
//}
//SendSlack(SUCCESS_TEMPLATE, message)
}
return nil
func (u *Slack) OnSuccess(s *types.Service) {
}
// ON SAVE OR UPDATE OF THE NOTIFIER FORM
func (u *Slack) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here
u.Test()
return nil
}

View File

@ -50,37 +50,31 @@ func init() {
Id: TWILIO_ID,
Method: TWILIO_METHOD,
Form: []NotificationForm{{
Id: 3,
Type: "text",
Title: "Account Sid",
Placeholder: "Insert your Twilio Account Sid",
DbField: "api_key",
}, {
Id: 3,
Type: "text",
Title: "Account Token",
Placeholder: "Insert your Twilio Account Token",
DbField: "api_secret",
}, {
Id: 3,
Type: "text",
Title: "SMS to Phone Number",
Placeholder: "+18555555555",
DbField: "Var1",
}, {
Id: 3,
Type: "text",
Title: "From Phone Number",
Placeholder: "+18555555555",
DbField: "Var2",
}}},
}
add(twilio)
}
// Select Obj
func (u *Twilio) Select() *Notification {
return u.Notification
err := AddNotifier(twilio)
if err != nil {
utils.Log(3, err)
}
}
func (u *Twilio) postUrl() string {
@ -104,6 +98,7 @@ func (u *Twilio) Init() error {
}
func (u *Twilio) Test() error {
utils.Log(1, "Twilio notifier loaded")
msg := fmt.Sprintf("You're Statup Twilio Notifier is working correctly!")
SendTwilio(msg)
return nil
@ -152,20 +147,16 @@ func SendTwilio(data string) error {
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Twilio) OnFailure(s *types.Service) error {
func (u *Twilio) OnFailure(s *types.Service, f *types.Failure) {
if u.Enabled {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
SendTwilio(msg)
}
return nil
}
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
func (u *Twilio) OnSuccess(s *types.Service) error {
if u.Enabled {
func (u *Twilio) OnSuccess(s *types.Service) {
}
return nil
}
// ON SAVE OR UPDATE OF THE NOTIFIER FORM

View File

@ -16,7 +16,8 @@
package plugin
import (
"github.com/hunterlong/statup/types"
"github.com/jinzhu/gorm"
"net/http"
"upper.io/db.v3/lib/sqlbuilder"
)
@ -36,8 +37,32 @@ var (
DB sqlbuilder.Database
)
type Routing struct {
URL string
Method string
Handler func(http.ResponseWriter, *http.Request)
}
type Info struct {
Name string
Description string
Form string
}
type Database *gorm.DB
type Plugin struct {
Name string
Description string
}
type PluginDatabase interface {
Database(gorm.DB)
Update() error
}
type PluginInfo struct {
i *types.Info
i *Info
}
func SetDatabase(database sqlbuilder.Database) {

View File

@ -25,17 +25,17 @@
<div class="row stats_area mb-5">
<div class="col-4">
<span class="lg_number">{{ .ServicesCount }}</span>
<span class="lg_number">{{ CoreApp.ServicesCount }}</span>
Total Services
</div>
<div class="col-4">
<span class="lg_number">{{ .Count24HFailures }}</span>
<span class="lg_number">{{ CoreApp.Count24HFailures }}</span>
Failures last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">{{ .CountOnline }}</span>
<span class="lg_number">{{ CoreApp.CountOnline }}</span>
Online Services
</div>
</div>
@ -94,4 +94,4 @@
{{end}}
</body>
</html>
</html>

View File

@ -56,7 +56,9 @@
</div>
</div>
<canvas id="service" width="400" height="120"></canvas>
<div class="chart-container">
<canvas id="service_{{ .Id }}"></canvas>
</div>
{{ if .LimitedFailures }}
<div class="list-group mt-3 mb-4">
@ -214,67 +216,21 @@
</div>
</div>
{{template "footer"}}
<script>
var ctx = document.getElementById("service").getContext('2d');
var chartdata = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Response Time (Milliseconds)',
data: {{js .GraphData}},
backgroundColor: [
'rgba(47, 206, 30, 0.92)'
],
borderColor: [
'rgb(47, 171, 34)'
],
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display:false
}
}],
xAxes: [{
type: 'time',
distribution: 'series',
gridLines: {
display:false
}
}]
},
elements: {
point: {
radius: 0
}
}
}
});
</script>
{{if USE_CDN}}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="https://assets.statup.io/main.js"></script>
{{ else }}
<script src="/js/jquery-3.3.1.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/Chart.bundle.min.js"></script>
<script src="/js/main.js"></script>
{{end}}
<script src="/charts/{{.Id}}.js"></script>
</body>
</html>

View File

@ -32,7 +32,7 @@
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
<a class="nav-link" id="v-pills-style-tab" data-toggle="pill" href="#v-pills-style" role="tab" aria-controls="v-pills-style" aria-selected="false">Theme Editor</a>
{{ range .Communications }}
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Method}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Method}}" role="tab" aria-controls="v-pills-{{underscore .Method}}" aria-selected="false">{{.Method}}</a>
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Select.Method}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Select.Method}}" role="tab" aria-controls="v-pills-{{underscore .Select.Method}}" aria-selected="false">{{.Select.Method}}</a>
{{ end }}
<a class="nav-link" id="v-pills-browse-tab" data-toggle="pill" href="#v-pills-browse" role="tab" aria-controls="v-pills-home" aria-selected="false">Browse Plugins</a>
<a class="nav-link d-none" id="v-pills-backups-tab" data-toggle="pill" href="#v-pills-backups" role="tab" aria-controls="v-pills-backups" aria-selected="false">Backups</a>
@ -132,39 +132,39 @@
{{end}}
</div>
{{ range .Communications }}
<div class="tab-pane" id="v-pills-{{underscore .Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore .Method }}-tab">
<form method="POST" action="/settings/notifier/{{ .Id }}">
{{$n := .Select}}
<div class="tab-pane" id="v-pills-{{underscore $n.Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore $n.Method }}-tab">
<form method="POST" action="/settings/notifier/{{ $n.Method }}">
{{range .Form}}
<div class="form-group">
<label class="text-capitalize" for="{{underscore .Title}}">{{.Title}}</label>
<input type="{{.Type}}" name="{{underscore .DbField}}" class="form-control" value="{{ .Value }}" id="{{underscore .Title}}" placeholder="{{.Placeholder}}">
<input type="{{.Type}}" name="{{underscore .DbField}}" class="form-control" value="{{ $n.GetValue .DbField }}" id="{{underscore .Title}}" placeholder="{{.Placeholder}}">
</div>
{{end}}
<div class="form-group">
<label class="text-capitalize" for="limits_per_hour_{{underscore .Method }}">Limits per Hour</label>
<input type="number" name="limits" class="form-control" value="{{.LimitValue}}" id="limits_per_hour_{{underscore .Method }}" min="1" max="60" placeholder="How many messages can send per hour">
<label class="text-capitalize" for="limits_per_hour_{{underscore $n.Method }}">Limits per Hour</label>
<input type="number" name="limits" class="form-control" value="{{$n.LimitValue}}" id="limits_per_hour_{{underscore $n.Method }}" min="1" max="60" placeholder="How many messages can send per hour">
</div>
<div class="form-group row">
<div class="col-sm-6">
<span class="switch">
<input type="checkbox" name="enable" class="switch" id="switch-{{ .Method }}" {{if .Enabled}}checked{{end}}>
<label for="switch-{{ .Method }}">Enable {{ .Method }}</label>
<input type="checkbox" name="enable" class="switch" id="switch-{{ $n.Method }}" {{if $n.Enabled}}checked{{end}}>
<label for="switch-{{ $n.Method }}">Enable {{ $n.Method }}</label>
</span>
</div>
<div class="col-sm-6">
<button type="submit" class="btn btn-primary btn-block text-capitalize">Save {{ .Method }} Settings</button>
<button type="submit" class="btn btn-primary btn-block text-capitalize">Save {{ $n.Method }} Settings</button>
</div>
</div>
</form>
{{ if .Logs }}
Sent {{.SentLastHour}} out of {{.LimitValue}} in the last hour<br>
{{ range .Logs }}
{{ if $n.Logs }}
Sent {{$n.SentLastHour}} out of {{$n.LimitValue}} in the last hour<br>
{{ range $n.Logs }}
<div class="card mt-1">
<div class="card-body">
{{.Message}}

View File

@ -21,44 +21,6 @@ import (
"time"
)
type PluginInfo struct {
Info Info
PluginActions
}
type Routing struct {
URL string
Method string
Handler func(http.ResponseWriter, *http.Request)
}
type Info struct {
Name string
Description string
Form string
}
type PluginActions interface {
GetInfo() Info
GetForm() string
OnLoad(db gorm.DB)
SetInfo(map[string]interface{}) Info
Routes() []Routing
OnSave(map[string]interface{})
OnFailure(map[string]interface{})
OnSuccess(map[string]interface{})
OnSettingsSaved(map[string]interface{})
OnNewUser(map[string]interface{})
OnNewService(map[string]interface{})
OnUpdatedService(map[string]interface{})
OnDeletedService(map[string]interface{})
OnInstall(map[string]interface{})
OnUninstall(map[string]interface{})
OnBeforeRequest(map[string]interface{})
OnAfterRequest(map[string]interface{})
OnShutdown()
}
type AllNotifiers interface{}
// Hit struct is a 'successful' ping or web response entry for a service.
@ -89,6 +51,44 @@ type DbConfig struct {
Location string `yaml:"location"`
}
type Info struct {
Name string
Description string
Form string
}
type PluginInfo struct {
Info Info
PluginActions
}
type Routing struct {
URL string
Method string
Handler func(http.ResponseWriter, *http.Request)
}
type PluginActions interface {
GetInfo() Info
GetForm() string
OnLoad(db gorm.DB)
SetInfo(map[string]interface{}) Info
Routes() []Routing
OnSave(map[string]interface{})
OnFailure(map[string]interface{})
OnSuccess(map[string]interface{})
OnSettingsSaved(map[string]interface{})
OnNewUser(map[string]interface{})
OnNewService(map[string]interface{})
OnUpdatedService(map[string]interface{})
OnDeletedService(map[string]interface{})
OnInstall(map[string]interface{})
OnUninstall(map[string]interface{})
OnBeforeRequest(map[string]interface{})
OnAfterRequest(map[string]interface{})
OnShutdown()
}
type PluginRepos struct {
Plugins []PluginJSON
}