tests - Shell Command Notifier - Annoucements in sample/seed

pull/94/head
Hunter Long 2018-11-06 23:53:39 -08:00
parent c95e3b85a5
commit 28880e2c5e
33 changed files with 620 additions and 386 deletions

View File

@ -1,4 +1,4 @@
VERSION=0.79.3
VERSION=0.79.4
BINARY_NAME=statup
GOPATH:=$(GOPATH)
GOCMD=go

View File

@ -291,7 +291,7 @@ func RunSelectAllNotifiers(t *testing.T) {
notifier.SetDB(core.DbSession)
core.CoreApp.Notifications = notifier.Load()
assert.Nil(t, err)
assert.Equal(t, 7, len(core.CoreApp.Notifications))
assert.Equal(t, 8, len(core.CoreApp.Notifications))
}
func RunUserSelectAll(t *testing.T) {
@ -305,7 +305,7 @@ func RunUserCreate(t *testing.T) {
Username: "hunterlong",
Password: "password123",
Email: "info@gmail.com",
Admin: utils.NullBool(true),
Admin: types.NewNullBool(true),
})
id, err := user.Create()
assert.Nil(t, err)
@ -314,7 +314,7 @@ func RunUserCreate(t *testing.T) {
Username: "superadmin",
Password: "admin",
Email: "info@adminer.com",
Admin: utils.NullBool(true),
Admin: types.NewNullBool(true),
})
id, err = user2.Create()
assert.Nil(t, err)

View File

@ -16,7 +16,6 @@
package core
import (
"database/sql"
"errors"
"fmt"
"github.com/go-yaml/yaml"
@ -72,7 +71,7 @@ func LoadUsingEnv() (*DbConfig, error) {
CoreApp.Name = os.Getenv("NAME")
CoreApp.Domain = os.Getenv("DOMAIN")
CoreApp.DbConnection = Configs.DbConn
CoreApp.UseCdn = sql.NullBool{os.Getenv("USE_CDN") == "true", true}
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
err := Configs.Connect(true, utils.Directory)
if err != nil {
@ -94,7 +93,7 @@ func LoadUsingEnv() (*DbConfig, error) {
Username: "admin",
Password: "admin",
Email: "info@admin.com",
Admin: sql.NullBool{true, true},
Admin: types.NewNullBool(true),
})
_, err := admin.Create()

View File

@ -16,7 +16,6 @@
package core
import (
"database/sql"
"errors"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/source"
@ -148,7 +147,7 @@ func SelectCore() (*Core, error) {
}
CoreApp.DbConnection = Configs.DbConn
CoreApp.Version = VERSION
CoreApp.UseCdn = sql.NullBool{os.Getenv("USE_CDN") == "true", true}
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
return CoreApp, db.Error
}

View File

@ -17,9 +17,9 @@ package core
import (
"bytes"
"database/sql"
"fmt"
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"html/template"
)
@ -33,7 +33,7 @@ func ExportIndexHTML() string {
source.Assets()
injectDatabase()
CoreApp.SelectAllServices(false)
CoreApp.UseCdn = sql.NullBool{true, true}
CoreApp.UseCdn = types.NewNullBool(true)
for _, srv := range CoreApp.Services {
service := srv.(*Service)
service.Check(true)

View File

@ -16,7 +16,6 @@
package notifier
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
@ -47,7 +46,7 @@ type Notification struct {
Var2 string `gorm:"not null;column:var2" json:"var2,omitempty"`
ApiKey string `gorm:"not null;column:api_key" json:"api_key,omitempty"`
ApiSecret string `gorm:"not null;column:api_secret" json:"api_secret,omitempty"`
Enabled sql.NullBool `gorm:"column:enabled;type:boolean;default:false" json:"enabled"`
Enabled types.NullBool `gorm:"column:enabled;type:boolean;default:false" json:"enabled"`
Limits int `gorm:"not null;column:limits" json:"limits"`
Removable bool `gorm:"column:removable" json:"-"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`

View File

@ -138,7 +138,7 @@ func TestNotification_Update(t *testing.T) {
func TestEnableNotification(t *testing.T) {
notifier, err := SelectNotification(example)
assert.Nil(t, err)
notifier.Enabled = utils.NullBool(true)
notifier.Enabled = types.NewNullBool(true)
updated, err := Update(example, notifier)
assert.Nil(t, err)
assert.True(t, updated.Enabled.Bool)
@ -180,57 +180,57 @@ func TestOnSave(t *testing.T) {
func TestOnSuccess(t *testing.T) {
OnSuccess(service)
assert.Equal(t, 7, len(example.Queue))
assert.Equal(t, 2, len(example.Queue))
}
func TestOnFailure(t *testing.T) {
OnFailure(service, failure)
assert.Equal(t, 8, len(example.Queue))
assert.Equal(t, 3, len(example.Queue))
}
func TestOnNewService(t *testing.T) {
OnNewService(service)
assert.Equal(t, 9, len(example.Queue))
assert.Equal(t, 4, len(example.Queue))
}
func TestOnUpdatedService(t *testing.T) {
OnUpdatedService(service)
assert.Equal(t, 10, len(example.Queue))
assert.Equal(t, 5, len(example.Queue))
}
func TestOnDeletedService(t *testing.T) {
OnDeletedService(service)
assert.Equal(t, 11, len(example.Queue))
assert.Equal(t, 6, len(example.Queue))
}
func TestOnNewUser(t *testing.T) {
OnNewUser(user)
assert.Equal(t, 12, len(example.Queue))
assert.Equal(t, 7, len(example.Queue))
}
func TestOnUpdatedUser(t *testing.T) {
OnUpdatedUser(user)
assert.Equal(t, 13, len(example.Queue))
assert.Equal(t, 8, len(example.Queue))
}
func TestOnDeletedUser(t *testing.T) {
OnDeletedUser(user)
assert.Equal(t, 14, len(example.Queue))
assert.Equal(t, 9, len(example.Queue))
}
func TestOnUpdatedCore(t *testing.T) {
OnUpdatedCore(core)
assert.Equal(t, 15, len(example.Queue))
assert.Equal(t, 10, len(example.Queue))
}
func TestOnUpdatedNotifier(t *testing.T) {
OnUpdatedNotifier(example.Select())
assert.Equal(t, 16, len(example.Queue))
assert.Equal(t, 11, len(example.Queue))
}
func TestRunAllQueueAndStop(t *testing.T) {
assert.True(t, example.IsRunning())
assert.Equal(t, 16, len(example.Queue))
assert.Equal(t, 11, len(example.Queue))
go Queue(example)
time.Sleep(13 * time.Second)
assert.NotZero(t, len(example.Queue))

View File

@ -16,7 +16,6 @@
package core
import (
"database/sql"
"fmt"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
@ -61,11 +60,11 @@ func InsertSampleData() error {
Name: "JSON API Tester",
Domain: "https://jsonplaceholder.typicode.com/posts",
ExpectedStatus: 201,
Expected: utils.NullString(`(title)": "((\\"|[statup])*)"`),
Expected: types.NewNullString(`(title)": "((\\"|[statup])*)"`),
Interval: 30,
Type: "http",
Method: "POST",
PostData: utils.NullString(`{ "title": "statup", "body": "bar", "userId": 19999 }`),
PostData: types.NewNullString(`{ "title": "statup", "body": "bar", "userId": 19999 }`),
Timeout: 30,
Order: 4,
})
@ -158,7 +157,7 @@ func insertSampleCore() error {
Domain: "http://localhost:8080",
Version: "test",
CreatedAt: time.Now(),
UseCdn: sql.NullBool{false, true},
UseCdn: types.NewNullBool(false),
}
query := coreDB().Create(core)
return query.Error
@ -170,14 +169,14 @@ func insertSampleUsers() {
Username: "testadmin",
Password: "password123",
Email: "info@betatude.com",
Admin: sql.NullBool{true, true},
Admin: types.NewNullBool(true),
})
u3 := ReturnUser(&types.User{
Username: "testadmin2",
Password: "password123",
Email: "info@adminhere.com",
Admin: sql.NullBool{true, true},
Admin: types.NewNullBool(true),
})
u2.Create()

View File

@ -82,6 +82,7 @@ func SelectAllUsers() ([]*user, error) {
db := usersDB().Find(&users)
if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to load all users. %v", db.Error))
return nil, db.Error
}
return users, db.Error
}

View File

@ -17,7 +17,6 @@ package core
import (
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/stretchr/testify/assert"
"testing"
)
@ -27,7 +26,7 @@ func TestCreateUser(t *testing.T) {
Username: "hunter",
Password: "password123",
Email: "test@email.com",
Admin: utils.NullBool(true),
Admin: types.NewNullBool(true),
})
userId, err := user.Create()
assert.Nil(t, err)
@ -71,7 +70,7 @@ func TestCreateUser2(t *testing.T) {
Username: "hunterlong",
Password: "password123",
Email: "user@email.com",
Admin: utils.NullBool(true),
Admin: types.NewNullBool(true),
})
userId, err := user.Create()
assert.Nil(t, err)

View File

@ -16,7 +16,6 @@
package handlers
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/gorilla/mux"
@ -297,7 +296,10 @@ func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
users, _ := core.SelectAllUsers()
users, err := core.SelectAllUsers()
if err != nil {
utils.Log(3, err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
@ -311,13 +313,13 @@ func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&user)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
newUser := core.ReturnUser(user)
uId, err := newUser.Create()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
output := apiResponse{
@ -368,7 +370,7 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
notifer.Port = notification.Port
notifer.Password = notification.Password
notifer.Username = notification.Username
notifer.Enabled = sql.NullBool{notification.Enabled.Bool, true}
notifer.Enabled = notification.Enabled
notifer.ApiKey = notification.ApiKey
notifer.ApiSecret = notification.ApiSecret
@ -382,6 +384,101 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(notifer)
}
func apiAllMessagesHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
messages, err := core.SelectMessages()
if err != nil {
http.Error(w, fmt.Sprintf("error fetching all messages: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(messages)
}
func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
if err != nil {
http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(message)
}
func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
if err != nil {
http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
return
}
err = message.Delete()
if err != nil {
http.Error(w, fmt.Sprintf("message #%v could not be deleted %v", vars["id"], err), http.StatusInternalServerError)
return
}
output := apiResponse{
Object: "message",
Method: "delete",
Id: message.Id,
Status: "success",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(output)
}
func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
vars := mux.Vars(r)
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
if err != nil {
http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
return
}
var messageBody *types.Message
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&messageBody)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
messageBody.Id = message.Id
message = core.ReturnMessage(messageBody)
_, err = message.Update()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
output := apiResponse{
Object: "message",
Method: "update",
Id: message.Id,
Status: "success",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(output)
}
func isAPIAuthorized(r *http.Request) bool {
if os.Getenv("GO_ENV") == "test" {
return true

View File

@ -16,7 +16,6 @@
package handlers
import (
"database/sql"
"encoding/json"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
@ -109,7 +108,7 @@ func DesktopInit(ip string, port int) {
Username: config.Username,
Password: config.Password,
Email: config.Email,
Admin: sql.NullBool{true, true},
Admin: types.NewNullBool(true),
})
admin.Create()

View File

@ -97,7 +97,7 @@ func updateMessageHandler(w http.ResponseWriter, r *http.Request) {
message.Title = title
message.Description = description
message.NotifyUsers = utils.NullBool(notifyUsers == "on")
message.NotifyUsers = types.NewNullBool(notifyUsers == "on")
message.NotifyMethod = notifyMethod
message.StartOn = start.UTC()
message.EndOn = end.UTC()
@ -134,7 +134,7 @@ func createMessageHandler(w http.ResponseWriter, r *http.Request) {
StartOn: start.UTC(),
EndOn: end.UTC(),
ServiceId: serviceId,
NotifyUsers: utils.NullBool(notifyUsers == "on"),
NotifyUsers: types.NewNullBool(notifyUsers == "on"),
NotifyMethod: notifyMethod,
NotifyBefore: before,
})

View File

@ -176,7 +176,7 @@ func TestApiAllUsersHandler(t *testing.T) {
assert.Equal(t, 200, rr.Code)
var obj []types.User
formatJSON(body, &obj)
assert.Equal(t, true, obj[0].Admin)
assert.Equal(t, true, obj[0].Admin.Bool)
assert.Equal(t, "admin", obj[0].Username)
}
@ -191,6 +191,7 @@ func TestApiCreateUserHandler(t *testing.T) {
body := rr.Body.String()
var obj apiResponse
formatJSON(body, &obj)
t.Log(body)
assert.Equal(t, 200, rr.Code)
assert.Contains(t, "create", obj.Method)
assert.Contains(t, "success", obj.Status)
@ -203,8 +204,9 @@ func TestApiViewUserHandler(t *testing.T) {
assert.Equal(t, 200, rr.Code)
var obj types.User
formatJSON(body, &obj)
t.Log(body)
assert.Equal(t, "admin", obj.Username)
assert.Equal(t, true, obj.Admin)
assert.Equal(t, true, obj.Admin.Bool)
}
func TestApiUpdateUserHandler(t *testing.T) {
@ -220,7 +222,7 @@ func TestApiUpdateUserHandler(t *testing.T) {
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "adminupdated", obj.Username)
assert.Equal(t, true, obj.Admin)
assert.Equal(t, true, obj.Admin.Bool)
}
func TestApiDeleteUserHandler(t *testing.T) {

View File

@ -121,10 +121,18 @@ func Router() *mux.Router {
r.Handle("/api/users/{id}", http.HandlerFunc(apiUserUpdateHandler)).Methods("POST")
r.Handle("/api/users/{id}", http.HandlerFunc(apiUserDeleteHandler)).Methods("DELETE")
// API Notifier Routes
// API NOTIFIER Routes
r.Handle("/api/notifier/{notifier}", http.HandlerFunc(apiNotifierGetHandler)).Methods("GET")
r.Handle("/api/notifier/{notifier}", http.HandlerFunc(apiNotifierUpdateHandler)).Methods("POST")
// API MESSAGES Routes
r.Handle("/api/messages", http.HandlerFunc(apiAllMessagesHandler)).Methods("GET")
r.Handle("/api/messages", http.HandlerFunc(apiNotifierUpdateHandler)).Methods("POST")
r.Handle("/api/messages/{id}", http.HandlerFunc(apiMessageGetHandler)).Methods("GET")
r.Handle("/api/messages/{id}", http.HandlerFunc(apiMessageUpdateHandler)).Methods("POST")
r.Handle("/api/messages/{id}", http.HandlerFunc(apiMessageDeleteHandler)).Methods("DELETE")
// API Generic Routes
r.Handle("/metrics", http.HandlerFunc(prometheusHandler))
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
r.Handle("/tray", http.HandlerFunc(trayHandler))

View File

@ -104,12 +104,12 @@ func createServiceHandler(w http.ResponseWriter, r *http.Request) {
Name: name,
Domain: domain,
Method: method,
Expected: utils.NullString(expected),
Expected: types.NewNullString(expected),
ExpectedStatus: status,
Interval: interval,
Type: checkType,
Port: port,
PostData: utils.NullString(postData),
PostData: types.NewNullString(postData),
Timeout: timeout,
Order: order,
})
@ -199,11 +199,11 @@ func servicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
service.Domain = domain
service.Method = method
service.ExpectedStatus = status
service.Expected = utils.NullString(expected)
service.Expected = types.NewNullString(expected)
service.Interval = interval
service.Type = checkType
service.Port = port
service.PostData = utils.NullString(postData)
service.PostData = types.NewNullString(postData)
service.Timeout = timeout
service.Order = order

View File

@ -16,12 +16,12 @@
package handlers
import (
"database/sql"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"net/http"
"net/url"
@ -57,7 +57,7 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
}
footer := r.PostForm.Get("footer")
if footer != app.Footer.String {
app.Footer = utils.NullString(footer)
app.Footer = types.NewNullString(footer)
}
domain := r.PostForm.Get("domain")
if domain != app.Domain {
@ -67,7 +67,7 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
timeFloat, _ := strconv.ParseFloat(timezone, 10)
app.Timezone = float32(timeFloat)
app.UseCdn = utils.NullBool(r.PostForm.Get("enable_cdn") == "on")
app.UseCdn = types.NewNullBool(r.PostForm.Get("enable_cdn") == "on")
core.CoreApp, _ = core.UpdateCore(app)
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
executeResponse(w, r, "settings.html", core.CoreApp, "/settings")
@ -189,7 +189,7 @@ func saveNotificationHandler(w http.ResponseWriter, r *http.Request) {
if limits != 0 {
notifer.Limits = limits
}
notifer.Enabled = sql.NullBool{enabled == "on", true}
notifer.Enabled = types.NewNullBool(enabled == "on")
_, err = notifier.Update(notif, notifer)
if err != nil {
@ -255,7 +255,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
if limits != 0 {
notifer.Limits = limits
}
notifer.Enabled = sql.NullBool{enabled == "on", true}
notifer.Enabled = types.NewNullBool(enabled == "on")
err = notif.(notifier.Tester).OnTest()
if err == nil {

View File

@ -16,7 +16,6 @@
package handlers
import (
"database/sql"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
@ -114,7 +113,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
Username: config.Username,
Password: config.Password,
Email: config.Email,
Admin: sql.NullBool{true, true},
Admin: types.NewNullBool(true),
})
admin.Create()

View File

@ -16,7 +16,6 @@
package handlers
import (
"database/sql"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statup/core"
@ -64,7 +63,7 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
user.Username = r.PostForm.Get("username")
user.Email = r.PostForm.Get("email")
isAdmin := r.PostForm.Get("admin") == "on"
user.Admin = sql.NullBool{isAdmin, true}
user.Admin = types.NewNullBool(isAdmin)
password := r.PostForm.Get("password")
if password != "##########" {
user.Password = utils.HashPassword(password)
@ -97,7 +96,7 @@ func createUserHandler(w http.ResponseWriter, r *http.Request) {
Username: username,
Password: password,
Email: email,
Admin: sql.NullBool{admin == "on", true},
Admin: types.NewNullBool(admin == "on"),
})
_, err := user.Create()
if err != nil {

164
notifiers/command.go Normal file
View File

@ -0,0 +1,164 @@
// 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 (
"errors"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"io"
"os"
"os/exec"
"time"
)
type commandLine struct {
*notifier.Notification
}
var command = &commandLine{&notifier.Notification{
Method: "command",
Title: "Shell Command",
Description: "Shell Command allows you to run customize shell or bash commands on the local machine it's running on.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(1 * time.Second),
Icon: "fas fa-command-alt",
Host: "sh",
Form: []notifier.NotificationForm{{
Type: "text",
Title: "Shell or Bash",
Placeholder: "sh",
DbField: "host",
SmallText: "You can use 'sh', 'bash' or even an absolute path for an application",
}, {
Type: "text",
Title: "Command to Run on OnSuccess",
Placeholder: "ping google.com",
DbField: "var1",
SmallText: "This command will run everytime a service is receiving a Successful event.",
}, {
Type: "text",
Title: "Command to Run on OnFailure",
Placeholder: "ping offline.com",
DbField: "var2",
SmallText: "This command will run everytime a service is receiving a Failing event.",
}}},
}
// init the command notifier
func init() {
err := notifier.AddNotifier(command)
if err != nil {
panic(err)
}
}
func runCommand(app, cmd string) (string, string, error) {
testCmd := exec.Command(app, "-c", cmd)
var stdout, stderr []byte
var errStdout, errStderr error
stdoutIn, _ := testCmd.StdoutPipe()
stderrIn, _ := testCmd.StderrPipe()
testCmd.Start()
go func() {
stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
}()
go func() {
stderr, errStderr = copyAndCapture(os.Stderr, stderrIn)
}()
err := testCmd.Wait()
if err != nil {
return "", "", err
}
if errStdout != nil || errStderr != nil {
return "", "", errors.New("failed to capture stdout or stderr")
}
outStr, errStr := string(stdout), string(stderr)
return outStr, errStr, err
}
// copyAndCapture captures the response from a terminal command
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
var out []byte
buf := make([]byte, 1024, 1024)
for {
n, err := r.Read(buf[:])
if n > 0 {
d := buf[:n]
out = append(out, d...)
_, err := w.Write(d)
if err != nil {
return out, err
}
}
if err != nil {
// Read returns io.EOF at the end of file, which is not an error for us
if err == io.EOF {
err = nil
}
return out, err
}
}
}
func (u *commandLine) Select() *notifier.Notification {
return u.Notification
}
// OnFailure for commandLine will trigger failing service
func (u *commandLine) OnFailure(s *types.Service, f *types.Failure) {
u.AddQueue(s.Id, u.Var2)
u.Online = false
}
// OnSuccess for commandLine will trigger successful service
func (u *commandLine) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetUniqueQueue(s.Id)
u.AddQueue(s.Id, u.Var1)
}
u.Online = true
}
// OnSave for commandLine triggers when this notifier has been saved
func (u *commandLine) OnSave() error {
u.AddQueue(0, u.Var1)
u.AddQueue(0, u.Var2)
return nil
}
// OnTest for commandLine triggers when this notifier has been saved
func (u *commandLine) OnTest() error {
in, out, err := runCommand(u.Host, u.Var1)
utils.Log(1, in)
utils.Log(1, out)
return err
}
// Send for commandLine will send message to expo command push notifications endpoint
func (u *commandLine) Send(msg interface{}) error {
cmd := msg.(string)
_, _, err := runCommand(u.Host, cmd)
return err
}

125
notifiers/command_test.go Normal file
View File

@ -0,0 +1,125 @@
// 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/core/notifier"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
const (
commandTest = "curl -I https://statup.io/"
)
func TestCommandNotifier(t *testing.T) {
t.Parallel()
command.Host = "sh"
command.Var1 = commandTest
command.Var2 = commandTest
currentCount = CountNotifiers()
t.Run("Load command", func(t *testing.T) {
command.Host = "sh"
command.Var1 = commandTest
command.Var2 = commandTest
command.Delay = time.Duration(100 * time.Millisecond)
command.Limits = 99
err := notifier.AddNotifier(command)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", command.Author)
assert.Equal(t, "sh", command.Host)
assert.Equal(t, commandTest, command.Var1)
assert.Equal(t, commandTest, command.Var2)
})
t.Run("Load command Notifier", func(t *testing.T) {
notifier.Load()
})
t.Run("command Notifier Tester", func(t *testing.T) {
assert.True(t, command.CanTest())
})
t.Run("command Within Limits", func(t *testing.T) {
ok, err := command.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("command OnFailure", func(t *testing.T) {
command.OnFailure(TestService, TestFailure)
assert.Equal(t, 1, len(command.Queue))
})
t.Run("command OnFailure multiple times", func(t *testing.T) {
for i := 0; i <= 50; i++ {
command.OnFailure(TestService, TestFailure)
}
assert.Equal(t, 52, len(command.Queue))
})
t.Run("command Check Offline", func(t *testing.T) {
assert.False(t, command.Online)
})
t.Run("command OnSuccess", func(t *testing.T) {
command.OnSuccess(TestService)
assert.Equal(t, 1, len(command.Queue))
})
t.Run("command Queue after being online", func(t *testing.T) {
assert.True(t, command.Online)
assert.Equal(t, 1, len(command.Queue))
})
t.Run("command OnSuccess Again", func(t *testing.T) {
assert.True(t, command.Online)
command.OnSuccess(TestService)
assert.Equal(t, 1, len(command.Queue))
go notifier.Queue(command)
time.Sleep(5 * time.Second)
assert.Equal(t, 0, len(command.Queue))
})
t.Run("command Within Limits again", func(t *testing.T) {
ok, err := command.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("command Send", func(t *testing.T) {
err := command.Send(commandTest)
assert.Nil(t, err)
assert.Equal(t, 0, len(command.Queue))
})
t.Run("command Test", func(t *testing.T) {
err := command.OnTest()
assert.Nil(t, err)
})
t.Run("command Queue", func(t *testing.T) {
go notifier.Queue(command)
time.Sleep(5 * time.Second)
assert.Equal(t, "sh", command.Host)
assert.Equal(t, commandTest, command.Var1)
assert.Equal(t, commandTest, command.Var2)
assert.Equal(t, 0, len(command.Queue))
})
}

View File

@ -164,7 +164,6 @@ func (u *email) Send(msg interface{}) error {
email := msg.(*emailOutgoing)
err := u.dialSend(email)
if err != nil {
utils.Log(3, fmt.Sprintf("email Notifier could not send email: %v", err))
return err
}
return nil
@ -246,7 +245,7 @@ func (u *email) OnTest() error {
Method: "GET",
Timeout: 20,
LastStatusCode: 200,
Expected: utils.NullString("test example"),
Expected: types.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour),
}
@ -271,7 +270,7 @@ func (u *email) dialSend(email *emailOutgoing) error {
m.SetHeader("Subject", email.Subject)
m.SetBody("text/html", email.Source)
if err := mailer.DialAndSend(m); err != nil {
utils.Log(3, fmt.Sprintf("email '%v' sent to: %v using the %v template (size: %v) %v", email.Subject, email.To, email.Template, len([]byte(email.Source)), err))
utils.Log(3, fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
return err
}
return nil

View File

@ -36,7 +36,7 @@ var TestService = &types.Service{
Name: "Interpol - All The Rage Back Home",
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
ExpectedStatus: 200,
Expected: "test example",
Expected: types.NewNullString("test example"),
Interval: 30,
Type: "http",
Method: "GET",

View File

@ -152,7 +152,7 @@ func (w *webhooker) OnTest() error {
Method: "GET",
Timeout: 20,
LastStatusCode: 404,
Expected: utils.NullString("test example"),
Expected: types.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour),
}

View File

@ -23,14 +23,14 @@ import (
)
var (
WEBHOOK_URL = "https://jsonplaceholder.typicode.com/posts"
webhookTestUrl = "https://jsonplaceholder.typicode.com/posts"
webhookMessage = `{ "title": "%service.Id", "body": "%service.Name", "online": %service.Online, "userId": 19999 }`
apiKey = "application/json"
fullMsg string
)
func init() {
webhook.Host = WEBHOOK_URL
webhook.Host = webhookTestUrl
webhook.Var1 = "POST"
}
@ -40,13 +40,13 @@ func TestWebhookNotifier(t *testing.T) {
currentCount = CountNotifiers()
t.Run("Load webhooker", func(t *testing.T) {
webhook.Host = WEBHOOK_URL
webhook.Host = webhookTestUrl
webhook.Delay = time.Duration(100 * time.Millisecond)
webhook.ApiKey = apiKey
err := notifier.AddNotifier(webhook)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", webhook.Author)
assert.Equal(t, WEBHOOK_URL, webhook.Host)
assert.Equal(t, webhookTestUrl, webhook.Host)
assert.Equal(t, apiKey, webhook.ApiKey)
})
@ -101,7 +101,7 @@ func TestWebhookNotifier(t *testing.T) {
t.Run("webhooker Queue", func(t *testing.T) {
go notifier.Queue(webhook)
time.Sleep(8 * time.Second)
assert.Equal(t, WEBHOOK_URL, webhook.Host)
assert.Equal(t, webhookTestUrl, webhook.Host)
assert.Equal(t, 1, len(webhook.Queue))
})

View File

@ -1,255 +0,0 @@
/*!
* Pikaday
* Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/
*/
// Variables
// Declare any of these variables before importing this SCSS file to easily override defaults
// Variables are namespaced with the pd (pikaday) prefix
// Colours
$pd-text-color: #333 !default;
$pd-title-color: #333 !default;
$pd-title-bg: #fff !default;
$pd-picker-bg: #fff !default;
$pd-picker-border: #ccc !default;
$pd-picker-border-bottom: #bbb !default;
$pd-picker-shadow: rgba(0,0,0,.5) !default;
$pd-th-color: #999 !default;
$pd-day-color: #666 !default;
$pd-day-bg: #f5f5f5 !default;
$pd-day-hover-color: #fff !default;
$pd-day-hover-bg: #ff8000 !default;
$pd-day-today-color: #33aaff !default;
$pd-day-selected-color: #fff !default;
$pd-day-selected-bg: #33aaff !default;
$pd-day-selected-shadow: #178fe5 !default;
$pd-day-disabled-color: #999 !default;
$pd-week-color: #999 !default;
// Font
$pd-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !default;
.pika-single {
z-index: 9999;
display: block;
position: relative;
color: $pd-text-color;
background: $pd-picker-bg;
border: 1px solid $pd-picker-border;
border-bottom-color: $pd-picker-border-bottom;
font-family: $pd-font-family;
&.is-hidden {
display: none;
}
&.is-bound {
position: absolute;
box-shadow: 0 5px 15px -5px $pd-picker-shadow;
}
}
// clear child float (pika-lendar), using the famous micro clearfix hack
// http://nicolasgallagher.com/micro-clearfix-hack/
.pika-single {
*zoom: 1;
&:before,
&:after {
content: " ";
display: table;
}
&:after { clear: both }
}
.pika-lendar {
float: left;
width: 240px;
margin: 8px;
}
.pika-title {
position: relative;
text-align: center;
select {
cursor: pointer;
position: absolute;
z-index: 9998;
margin: 0;
left: 0;
top: 5px;
filter: alpha(opacity=0);
opacity: 0;
}
}
.pika-label {
display: inline-block;
*display: inline;
position: relative;
z-index: 9999;
overflow: hidden;
margin: 0;
padding: 5px 3px;
font-size: 14px;
line-height: 20px;
font-weight: bold;
color: $pd-title-color;
background-color: $pd-title-bg;
}
.pika-prev,
.pika-next {
display: block;
cursor: pointer;
position: relative;
outline: none;
border: 0;
padding: 0;
width: 20px;
height: 30px;
text-indent: 20px; // hide text using text-indent trick, using width value (it's enough)
white-space: nowrap;
overflow: hidden;
background-color: transparent;
background-position: center center;
background-repeat: no-repeat;
background-size: 75% 75%;
opacity: .5;
*position: absolute;
*top: 0;
&:hover {
opacity: 1;
}
&.is-disabled {
cursor: default;
opacity: .2;
}
}
.pika-prev,
.is-rtl .pika-next {
float: left;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');
*left: 0;
}
.pika-next,
.is-rtl .pika-prev {
float: right;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');
*right: 0;
}
.pika-select {
display: inline-block;
*display: inline;
}
.pika-table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
border: 0;
th,
td {
width: 14.285714285714286%;
padding: 0;
}
th {
color: $pd-th-color;
font-size: 12px;
line-height: 25px;
font-weight: bold;
text-align: center;
}
abbr {
border-bottom: none;
cursor: help;
}
}
.pika-button {
cursor: pointer;
display: block;
-moz-box-sizing: border-box;
box-sizing: border-box;
outline: none;
border: 0;
margin: 0;
width: 100%;
padding: 5px;
color: $pd-day-color;
font-size: 12px;
line-height: 15px;
text-align: right;
background: $pd-day-bg;
.is-today & {
color: $pd-day-today-color;
font-weight: bold;
}
.is-selected & {
color: $pd-day-selected-color;
font-weight: bold;
background: $pd-day-selected-bg;
box-shadow: inset 0 1px 3px $pd-day-selected-shadow;
border-radius: 3px;
}
.is-disabled &,
.is-outside-current-month & {
color: $pd-day-disabled-color;
opacity: .3;
}
.is-disabled & {
pointer-events: none;
cursor: default;
}
&:hover {
color: $pd-day-hover-color;
background: $pd-day-hover-bg;
box-shadow: none;
border-radius: 3px;
}
.is-selection-disabled {
pointer-events: none;
cursor: default;
}
}
.pika-week {
font-size: 11px;
color: $pd-week-color;
}
.is-inrange .pika-button {
background: #D5E9F7;
}
.is-startrange .pika-button {
color: #fff;
background: #6CB31D;
box-shadow: none;
border-radius: 3px;
}
.is-endrange .pika-button {
color: #fff;
background: #33aaff;
box-shadow: none;
border-radius: 3px;
}

View File

@ -16,7 +16,6 @@
package types
import (
"database/sql"
"time"
)
@ -33,11 +32,11 @@ type Core struct {
ApiKey string `gorm:"column:api_key" json:"-"`
ApiSecret string `gorm:"column:api_secret" json:"-"`
Style string `gorm:"not null;column:style" json:"style,omitempty"`
Footer sql.NullString `gorm:"column:footer" json:"footer"`
Footer NullString `gorm:"column:footer" json:"footer"`
Domain string `gorm:"not null;column:domain" json:"domain"`
Version string `gorm:"column:version" json:"version"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn sql.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
@ -49,10 +48,3 @@ type Core struct {
AllPlugins []PluginActions `gorm:"-" json:"-"`
Notifications []AllNotifiers `gorm:"-" json:"-"`
}
//type CoreInterface interface {
// SelectAllServices() ([]*Service, error)
// Count24HFailures() uint64
// ServicesCount() int
// CountOnline() int
//}

View File

@ -33,6 +33,6 @@ type Failure struct {
}
type FailureInterface interface {
Ago() string // Ago returns a human readble timestamp
Ago() string // Ago returns a human readable timestamp
ParseError() string // ParseError returns a human readable error for a service failure
}

View File

@ -16,7 +16,6 @@
package types
import (
"database/sql"
"time"
)
@ -28,7 +27,7 @@ type Message struct {
StartOn time.Time `gorm:"column:start_on"`
EndOn time.Time `gorm:"column:end_on"`
ServiceId int64 `gorm:"index;column:service"`
NotifyUsers sql.NullBool `gorm:"column:notify_users"`
NotifyUsers NullBool `gorm:"column:notify_users"`
NotifyMethod string `gorm:"column:notify_method"`
NotifyBefore time.Duration `gorm:"column:notify_before"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`

121
types/null.go Normal file
View File

@ -0,0 +1,121 @@
// 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 types
import (
"database/sql"
"encoding/json"
)
// NewNullString returns a sql.NullString for JSON parsing
func NewNullString(s string) NullString {
return NullString{sql.NullString{s, true}}
}
// NewNullBool returns a sql.NullBool for JSON parsing
func NewNullBool(s bool) NullBool {
return NullBool{sql.NullBool{s, true}}
}
// NewNullInt64 returns a sql.NullInt64 for JSON parsing
func NewNullInt64(s int64) NullInt64 {
return NullInt64{sql.NullInt64{s, true}}
}
// NewNullFloat64 returns a sql.NullFloat64 for JSON parsing
func NewNullFloat64(s float64) NullFloat64 {
return NullFloat64{sql.NullFloat64{s, true}}
}
// NullInt64 is an alias for sql.NullInt64 data type
type NullInt64 struct {
sql.NullInt64
}
// NullBool is an alias for sql.NullBool data type
type NullBool struct {
sql.NullBool
}
// NullString is an alias for sql.NullString data type
type NullString struct {
sql.NullString
}
// NullFloat64 is an alias for sql.NullFloat64 data type
type NullFloat64 struct {
sql.NullFloat64
}
// MarshalJSON for NullInt64
func (ni *NullInt64) MarshalJSON() ([]byte, error) {
if !ni.Valid {
return []byte("null"), nil
}
return json.Marshal(ni.Int64)
}
// MarshalJSON for NullFloat64
func (ni *NullFloat64) MarshalJSON() ([]byte, error) {
if !ni.Valid {
return []byte("null"), nil
}
return json.Marshal(ni.Float64)
}
// MarshalJSON for NullBool
func (nb *NullBool) MarshalJSON() ([]byte, error) {
if !nb.Valid {
return []byte("null"), nil
}
return json.Marshal(nb.Bool)
}
// MarshalJSON for NullString
func (ns *NullString) MarshalJSON() ([]byte, error) {
if !ns.Valid {
return []byte("null"), nil
}
return json.Marshal(ns.String)
}
// Unmarshaler for NullInt64
func (nf *NullInt64) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &nf.Int64)
nf.Valid = (err == nil)
return err
}
// Unmarshaler for NullFloat64
func (nf *NullFloat64) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &nf.Float64)
nf.Valid = (err == nil)
return err
}
// Unmarshaler for NullBool
func (nf *NullBool) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &nf.Bool)
nf.Valid = (err == nil)
return err
}
// Unmarshaler for NullString
func (nf *NullString) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &nf.String)
nf.Valid = (err == nil)
return err
}

View File

@ -16,7 +16,6 @@
package types
import (
"database/sql"
"time"
)
@ -25,16 +24,16 @@ type Service struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
Domain string `gorm:"column:domain" json:"domain"`
Expected sql.NullString `gorm:"column:expected" json:"expected"`
Expected NullString `gorm:"column:expected" json:"expected"`
ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status"`
Interval int `gorm:"default:30;column:check_interval" json:"check_interval"`
Type string `gorm:"column:check_type" json:"type"`
Method string `gorm:"column:method" json:"method"`
PostData sql.NullString `gorm:"column:post_data" json:"post_data"`
PostData NullString `gorm:"column:post_data" json:"post_data"`
Port int `gorm:"not null;column:port" json:"port"`
Timeout int `gorm:"default:30;column:timeout" json:"timeout"`
Order int `gorm:"default:0;column:order_id" json:"order_id"`
AllowNotifications sql.NullBool `gorm:"default:false;column:allow_notifications" json:"allow_notifications"`
AllowNotifications NullBool `gorm:"default:false;column:allow_notifications" json:"allow_notifications"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Online bool `gorm:"-" json:"online"`

View File

@ -16,7 +16,6 @@
package types
import (
"database/sql"
"time"
)
@ -28,7 +27,7 @@ type User struct {
Email string `gorm:"type:varchar(100);unique;column:email" json:"-"`
ApiKey string `gorm:"column:api_key" json:"api_key"`
ApiSecret string `gorm:"column:api_secret" json:"-"`
Admin sql.NullBool `gorm:"column:administrator" json:"admin"`
Admin NullBool `gorm:"column:administrator" json:"admin"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
UserInterface `gorm:"-" json:"-"`

View File

@ -16,7 +16,6 @@
package utils
import (
"database/sql"
"errors"
"fmt"
"github.com/ararog/timeago"
@ -44,14 +43,6 @@ func init() {
}
}
func NullString(s string) sql.NullString {
return sql.NullString{s, true}
}
func NullBool(s bool) sql.NullBool {
return sql.NullBool{s, true}
}
func StringPoint(s string) *string {
val := new(string)
*val = s