notifier - moved core

pull/29/head
Hunter Long 2018-07-17 02:18:20 -07:00
parent 949ba3958a
commit d0ad55488d
25 changed files with 336 additions and 234 deletions

14
cli.go
View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv"
@ -106,9 +105,8 @@ func RunOnce() {
if err != nil {
utils.Log(4, err)
}
for _, ser := range core.CoreApp.Services {
s := ser.ToService()
out := core.ServiceCheck(s)
for _, s := range core.CoreApp.Services {
out := core.ServiceCheck(s.ToService())
fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online)
}
}
@ -129,7 +127,7 @@ func HelpEcho() {
fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup")
}
func TestPlugin(plug plugin.PluginActions) {
func TestPlugin(plug types.PluginActions) {
defer utils.DeleteFile("./.plugin_test.db")
RenderBoxes()
@ -160,7 +158,7 @@ func TestPlugin(plug plugin.PluginActions) {
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'")
fmt.Println(BRAKER)
core.OnSettingsSaved(core.CoreApp)
core.OnSettingsSaved(core.CoreApp.ToCore())
fmt.Println("\n" + BRAKER)
fmt.Println(POINT + "Sending 'OnNewService(Service)'")
core.OnNewService(core.SelectService(2).ToService())
@ -180,11 +178,11 @@ func TestPlugin(plug plugin.PluginActions) {
fmt.Println("\n" + BRAKER)
}
func FakeSeed(plug plugin.PluginActions) {
func FakeSeed(plug types.PluginActions) {
var err error
core.CoreApp = core.NewCore()
core.CoreApp.AllPlugins = []plugin.PluginActions{plug}
core.CoreApp.AllPlugins = []types.PluginActions{plug}
fmt.Printf("\n" + BRAKER)

View File

@ -20,10 +20,9 @@ func CheckServices() {
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
for _, ser := range CoreApp.Services {
s := ser.ToService()
obj := s
//go obj.StartCheckins()
obj.StopRoutine = make(chan struct{})
go CheckQueue(obj)
s.StopRoutine = make(chan struct{})
go CheckQueue(s)
}
}

View File

@ -16,8 +16,7 @@ func (c *Checkin) String() string {
func FindCheckin(api string) *types.Checkin {
for _, ser := range CoreApp.Services {
s := ser.ToService()
for _, c := range s.Checkins {
for _, c := range ser.ToService().Checkins {
if c.Api == api {
return c
}

View File

@ -85,7 +85,7 @@ func LoadUsingEnv() (*types.Config, error) {
DropDatabase()
CreateDatabase()
CoreApp = &Core{
CoreApp = &Core{Core: &types.Core{
Name: dbConfig.Project,
Description: dbConfig.Description,
Config: "config.yml",
@ -93,11 +93,11 @@ func LoadUsingEnv() (*types.Config, error) {
ApiSecret: utils.NewSHA1Hash(16),
Domain: dbConfig.Domain,
MigrationId: time.Now().Unix(),
}
}}
CoreApp.DbConnection = dbConfig.DbConn
err := CoreApp.Insert()
err := InsertCore(CoreApp)
if err != nil {
utils.Log(3, err)
}

View File

@ -3,7 +3,6 @@ package core
import (
"github.com/GeertJohan/go.rice"
"github.com/hunterlong/statup/notifiers"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
"github.com/pkg/errors"
"os"
@ -14,24 +13,8 @@ type PluginJSON types.PluginJSON
type PluginRepos types.PluginRepos
type Core struct {
Name string `db:"name" json:"name"`
Description string `db:"description" json:"name"`
Config string `db:"config" json:"-"`
ApiKey string `db:"api_key" json:"-"`
ApiSecret string `db:"api_secret" json:"-"`
Style string `db:"style" json:"-"`
Footer string `db:"footer" json:"-"`
Domain string `db:"domain" json:"domain,omitempty"`
Version string `db:"version" json:"version,omitempty"`
MigrationId int64 `db:"migration_id" json:"-"`
UseCdn bool `db:"use_cdn" json:"-"`
Services []*Service `json:"services,omitempty"`
Plugins []plugin.Info
Repos []PluginJSON
AllPlugins []plugin.PluginActions
Communications []notifiers.AllNotifiers
DbConnection string
started time.Time
*types.Core
Services []*Service
}
var (
@ -53,16 +36,21 @@ func init() {
func NewCore() *Core {
CoreApp = new(Core)
CoreApp.started = time.Now()
CoreApp.Core = new(types.Core)
CoreApp.Started = time.Now()
return CoreApp
}
func (c *Core) Insert() error {
func InsertCore(c *Core) error {
col := DbSession.Collection("core")
_, err := col.Insert(c)
_, err := col.Insert(c.Core)
return err
}
func (c *Core) ToCore() *types.Core {
return c.Core
}
func InitApp() {
SelectCore()
notifiers.Collections = DbSession.Collection("communication")
@ -72,9 +60,9 @@ func InitApp() {
go DatabaseMaintence()
}
func (c *Core) Update() (*Core, error) {
func UpdateCore(c *Core) (*Core, error) {
res := DbSession.Collection("core").Find().Limit(1)
err := res.Update(c)
err := res.Update(c.Core)
return c, err
}
@ -114,7 +102,7 @@ func (c Core) AllOnline() bool {
}
func SelectLastMigration() (int64, error) {
var c *Core
var c *types.Core
if DbSession == nil {
return 0, errors.New("Database connection has not been created yet")
}
@ -126,17 +114,16 @@ func SelectLastMigration() (int64, error) {
}
func SelectCore() (*Core, error) {
var c *Core
var c *types.Core
exists := DbSession.Collection("core").Exists()
if !exists {
return nil, errors.New("core database has not been setup yet.")
}
err := DbSession.Collection("core").Find().One(&c)
if err != nil {
return nil, err
}
CoreApp = c
CoreApp.Core = c
CoreApp.DbConnection = Configs.Connection
CoreApp.Version = VERSION
CoreApp.Services, _ = SelectAllServices()

View File

@ -119,7 +119,7 @@ func (c *DbConfig) Save() error {
DropDatabase()
CreateDatabase()
newCore := &Core{
newCore := &types.Core{
Name: c.Project,
Description: c.Description,
Config: "config.yml",
@ -131,7 +131,7 @@ func (c *DbConfig) Save() error {
col := DbSession.Collection("core")
_, err = col.Insert(newCore)
if err == nil {
CoreApp = newCore
CoreApp = &Core{Core: newCore}
}
CoreApp, err = SelectCore()
@ -204,7 +204,7 @@ func RunDatabaseUpgrades() error {
panic(err)
}
CoreApp.MigrationId = currentMigration
CoreApp.Update()
UpdateCore(CoreApp)
}
return err
}

View File

@ -3,7 +3,6 @@ package core
import (
"github.com/fatih/structs"
"github.com/hunterlong/statup/notifiers"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
"upper.io/db.v3/lib/sqlbuilder"
)
@ -28,7 +27,7 @@ func OnFailure(s *types.Service, f FailureData) {
notifiers.OnFailure(s)
}
func OnSettingsSaved(c *Core) {
func OnSettingsSaved(c *types.Core) {
for _, p := range CoreApp.AllPlugins {
p.OnSettingsSaved(structs.Map(c))
}
@ -58,11 +57,11 @@ func OnUpdateService(s *types.Service) {
}
}
func SelectPlugin(name string) plugin.PluginActions {
func SelectPlugin(name string) types.PluginActions {
for _, p := range CoreApp.AllPlugins {
if p.GetInfo().Name == name {
return p
}
}
return plugin.PluginInfo{}
return types.PluginInfo{}
}

View File

@ -46,12 +46,16 @@ func DeleteFailures(u *types.Service) {
}
}
func (ser *Service) LimitedFailures() []*types.Failure {
func (ser *Service) LimitedFailures() []*Failure {
s := ser.ToService()
var fails []*types.Failure
var failArr []*Failure
col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id").Limit(10)
col.All(&fails)
return fails
for _, f := range fails {
failArr = append(failArr, MakeFailure(f))
}
return failArr
}
func reverseFailures(input []*types.Failure) []*types.Failure {

View File

@ -11,7 +11,7 @@ import (
)
type Service struct {
S interface{}
s *types.Service
}
type Failure struct {
@ -23,10 +23,10 @@ func serviceCol() db.Collection {
}
func SelectService(id int64) *Service {
for _, ser := range CoreApp.Services {
s := ser.ToService()
if s.Id == id {
return ser
for _, s := range CoreApp.Services {
ser := s.ToService()
if ser.Id == id {
return &Service{ser}
}
}
return nil
@ -64,7 +64,7 @@ func (s *Service) AvgTime() float64 {
}
func (ser *Service) Online24() float32 {
s := ser.S.(*types.Service)
s := ser.ToService()
total, _ := ser.TotalHits()
failed, _ := ser.TotalFailures24Hours()
if failed == 0 {
@ -91,12 +91,11 @@ type DateScan struct {
}
func (s *Service) ToService() *types.Service {
return s.S.(*types.Service)
return s.s
}
func NewService(s *types.Service) *Service {
ser := &Service{s}
return ser
return &Service{s}
}
func (ser *Service) SmallText() string {
@ -105,8 +104,8 @@ func (ser *Service) SmallText() string {
hits, _ := ser.LimitedHits()
if !s.Online {
if len(last) > 0 {
lastFailure := MakeFailure(last[0])
return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].CreatedAt.Format("Monday 3:04PM, Jan _2 2006"))
lastFailure := MakeFailure(last[0].ToFailure())
return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].ToFailure().CreatedAt.Format("Monday 3:04PM, Jan _2 2006"))
} else {
return fmt.Sprintf("%v is currently offline", s.Name)
}
@ -194,10 +193,10 @@ func (ser *Service) AvgUptime() string {
func RemoveArray(u *types.Service) []*Service {
var srvcs []*Service
for _, ser := range CoreApp.Services {
s := ser.ToService()
if s.Id != u.Id {
srvcs = append(srvcs, ser)
for _, s := range CoreApp.Services {
ser := s.ToService()
if ser.Id != u.Id {
srvcs = append(srvcs, s)
}
}
CoreApp.Services = srvcs
@ -241,16 +240,15 @@ func CreateService(u *types.Service) (int64, error) {
}
u.Id = uuid.(int64)
u.StopRoutine = make(chan struct{})
nn := &Service{u}
CoreApp.Services = append(CoreApp.Services, nn)
CoreApp.Services = append(CoreApp.Services, &Service{u})
return uuid.(int64), err
}
func CountOnline() int {
amount := 0
for _, ser := range CoreApp.Services {
s := ser.ToService()
if s.Online {
for _, s := range CoreApp.Services {
ser := s.ToService()
if ser.Online {
amount++
}
}

View File

@ -17,6 +17,21 @@ func ApiIndexHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(core.CoreApp)
}
func ApiRenewHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
var err error
core.CoreApp.ApiKey = utils.NewSHA1Hash(40)
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
core.CoreApp, err = core.UpdateCore(core.CoreApp)
if err != nil {
utils.Log(3, err)
}
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)

View File

@ -33,7 +33,7 @@ func Router() *mux.Router {
r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET")
r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST")
r.Handle("/user/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(SettingsHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST")
r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST")
r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET")
@ -42,6 +42,7 @@ func Router() *mux.Router {
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
r.Handle("/help", http.HandlerFunc(HelpHandler))
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
r.Handle("/api/renew", http.HandlerFunc(ApiRenewHandler))
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")

View File

@ -78,6 +78,9 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
serv := core.SelectService(utils.StringInt(vars["id"]))
fmt.Println(serv.ToService())
ExecuteResponse(w, r, "service.html", serv)
}
@ -94,9 +97,7 @@ func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write(badge)
}
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -9,23 +9,11 @@ import (
"net/http"
)
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
func SettingsHandler(w http.ResponseWriter, r *http.Request) {
if !IsAuthenticated(r) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//CoreApp.FetchPluginRepo()
//var pluginFields []PluginSelect
//
//for _, p := range allPlugins {
// fields := structs.Map(p.GetInfo())
//
// pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(), fields})
//}
//CoreApp.PluginFields = pluginFields
ExecuteResponse(w, r, "settings.html", core.CoreApp)
}
@ -56,8 +44,8 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
core.CoreApp.Domain = domain
}
core.CoreApp.UseCdn = (r.PostForm.Get("enable_cdn") == "on")
core.CoreApp.Update()
core.OnSettingsSaved(core.CoreApp)
core.CoreApp, _ = core.UpdateCore(core.CoreApp)
core.OnSettingsSaved(core.CoreApp.ToCore())
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
@ -105,8 +93,8 @@ func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) {
var2 := r.PostForm.Get("var2")
apiKey := r.PostForm.Get("api_key")
apiSecret := r.PostForm.Get("api_secret")
limits := int64(utils.StringInt(r.PostForm.Get("limits")))
notifer := notifiers.Select(utils.StringInt(notifierId))
limits := int(utils.StringInt(r.PostForm.Get("limits")))
notifer := notifiers.SelectNotifier(utils.StringInt(notifierId)).Select()
if host != "" {
notifer.Host = host

View File

@ -6,7 +6,7 @@ import (
"github.com/fatih/structs"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv"
"io/ioutil"
@ -123,8 +123,8 @@ func LoadPlugins(debug bool) {
utils.Log(1, structs.Map(symPlugin))
}
var plugActions plugin.PluginActions
plugActions, ok := symPlugin.(plugin.PluginActions)
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 {

View File

@ -20,7 +20,7 @@ const (
var (
emailer *Email
emailArray []string
emailQueue []*types.Email
emailQueue []*EmailOutgoing
emailBox *rice.Box
mailer *gomail.Dialer
)
@ -37,41 +37,35 @@ func init() {
Id: EMAIL_ID,
Method: EMAIL_METHOD,
Form: []NotificationForm{{
id: 1,
Id: 1,
Type: "text",
Title: "SMTP Host",
Placeholder: "Insert your SMTP Host here.",
DbField: "Host",
}, {
id: 1,
Id: 1,
Type: "text",
Title: "SMTP Username",
Placeholder: "Insert your SMTP Username here.",
DbField: "Username",
}, {
id: 1,
Id: 1,
Type: "password",
Title: "SMTP Password",
Placeholder: "Insert your SMTP Password here.",
DbField: "Password",
}, {
id: 1,
Id: 1,
Type: "number",
Title: "SMTP Port",
Placeholder: "Insert your SMTP Port here.",
DbField: "Port",
}, {
id: 1,
Id: 1,
Type: "text",
Title: "Outgoing Email Address",
Placeholder: "Insert your Outgoing Email Address",
DbField: "Var1",
}, {
id: 1,
Type: "number",
Title: "Limits per Hour",
Placeholder: "How many emails can it send per hour",
DbField: "Limits",
}},
}}
@ -108,7 +102,7 @@ func (u *Email) Init() error {
func (u *Email) Test() error {
if u.Enabled {
email := &types.Email{
email := &EmailOutgoing{
To: "info@socialeck.com",
Subject: "Test Email",
Template: "message.html",
@ -124,6 +118,16 @@ type emailMessage struct {
Service *types.Service
}
type EmailOutgoing struct {
To string
Subject string
Template string
From string
Data interface{}
Source string
Sent bool
}
// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS
func (u *Email) Run() error {
var sentAddresses []string
@ -132,16 +136,18 @@ func (u *Email) Run() error {
emailQueue = removeEmail(emailQueue, email)
continue
}
e := email
go func(email *types.Email) {
if u.CanSend() {
err := u.dialSend(email)
if err == nil {
email.Sent = true
sentAddresses = append(sentAddresses, email.To)
utils.Log(1, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v)", email.Subject, email.To, email.Template, len([]byte(email.Source))))
emailQueue = removeEmail(emailQueue, email)
u.Log(fmt.Sprintf("Subject: %v to %v", email.Subject, email.To))
} else {
utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err))
}
}(e)
}
}
time.Sleep(60 * time.Second)
if u.Enabled {
@ -158,7 +164,7 @@ func (u *Email) OnFailure(s *types.Service) error {
Service: s,
}
email := &types.Email{
email := &EmailOutgoing{
To: "info@socialeck.com",
Subject: fmt.Sprintf("Service %v is Failing", s.Name),
Template: "failure.html",
@ -190,10 +196,7 @@ func (u *Email) OnSave() error {
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Email) Install() error {
fmt.Println("installing emailer")
inDb, err := emailer.Notification.isInDatabase()
inDb, err := emailer.Notification.IsInDatabase()
if !inDb {
newNotifer, err := InsertDatabase(u.Notification)
if err != nil {
@ -205,7 +208,7 @@ func (u *Email) Install() error {
return err
}
func (u *Email) dialSend(email *types.Email) error {
func (u *Email) dialSend(email *EmailOutgoing) error {
fmt.Println("sending dailsend to emailer")
m := gomail.NewMessage()
m.SetHeader("From", email.From)
@ -219,7 +222,7 @@ func (u *Email) dialSend(email *types.Email) error {
return nil
}
func SendEmail(box *rice.Box, email *types.Email) {
func SendEmail(box *rice.Box, email *EmailOutgoing) {
source := EmailTemplate(box, email.Template, email.Data)
email.Source = source
emailQueue = append(emailQueue, email)
@ -243,8 +246,8 @@ func EmailTemplate(box *rice.Box, tmpl string, data interface{}) string {
return result
}
func removeEmail(emails []*types.Email, em *types.Email) []*types.Email {
var newArr []*types.Email
func removeEmail(emails []*EmailOutgoing, em *EmailOutgoing) []*EmailOutgoing {
var newArr []*EmailOutgoing
for _, e := range emails {
if e != em {
newArr = append(newArr, e)

View File

@ -10,28 +10,11 @@ import (
)
var (
AllCommunications []AllNotifiers
AllCommunications []types.AllNotifiers
Collections db.Collection
Logs []*NotificationLog
)
type AllNotifiers interface{}
func add(c interface{}) {
AllCommunications = append(AllCommunications, c)
}
func Load() []AllNotifiers {
utils.Log(1, "Loading notifiers")
var notifiers []AllNotifiers
for _, comm := range AllCommunications {
n := comm.(Notifier)
n.Init()
notifiers = append(notifiers, n)
n.Test()
}
return notifiers
}
type Notification struct {
Id int64 `db:"id,omitempty" json:"id"`
Method string `db:"method" json:"method"`
@ -44,7 +27,7 @@ type Notification struct {
ApiKey string `db:"api_key" json:"-"`
ApiSecret string `db:"api_secret" json:"-"`
Enabled bool `db:"enabled" json:"enabled"`
Limits int64 `db:"limits" json:"-"`
Limits int `db:"limits" json:"-"`
Removable bool `db:"removable" json:"-"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Form []NotificationForm
@ -62,14 +45,62 @@ type Notifier interface {
}
type NotificationForm struct {
id int64
Id int64
Type string
Title string
Placeholder string
DbField string
}
func (n *Notification) isInDatabase() (bool, error) {
type NotificationLog struct {
Notifier *Notification
Message string
Time utils.Timestamp
}
func add(c interface{}) {
AllCommunications = append(AllCommunications, c)
}
func Load() []types.AllNotifiers {
utils.Log(1, "Loading notifiers")
var notifiers []types.AllNotifiers
for _, comm := range AllCommunications {
n := comm.(Notifier)
n.Init()
notifiers = append(notifiers, n)
n.Test()
}
return notifiers
}
func (n *Notification) Log(msg string) {
log := &NotificationLog{
Notifier: n,
Message: msg,
Time: utils.Timestamp(time.Now()),
}
Logs = append(Logs, log)
}
func (n *Notification) Logs() []*NotificationLog {
var logs []*NotificationLog
for _, v := range Logs {
if v.Notifier.Id == n.Id {
logs = append(logs, v)
}
}
return reverseLogs(logs)
}
func reverseLogs(input []*NotificationLog) []*NotificationLog {
if len(input) == 0 {
return input
}
return append(reverseLogs(input[1:]), input[0])
}
func (n *Notification) IsInDatabase() (bool, error) {
return Collections.Find("id", n.Id).Exists()
}
@ -87,6 +118,7 @@ func (n *Notification) Update() (*Notification, error) {
func InsertDatabase(n *Notification) (int64, error) {
n.CreatedAt = time.Now()
n.Limits = 3
newId, err := Collections.Insert(n)
if err != nil {
return 0, err
@ -94,18 +126,6 @@ func InsertDatabase(n *Notification) (int64, error) {
return newId.(int64), err
}
func Select(id int64) *Notification {
var notifier *Notification
for _, n := range AllCommunications {
notif := n.(Notifier)
notifier = notif.Select()
if notifier.Id == id {
return notifier
}
}
return notifier
}
func SelectNotifier(id int64) Notifier {
var notifier Notifier
for _, n := range AllCommunications {
@ -118,9 +138,33 @@ func SelectNotifier(id int64) Notifier {
return notifier
}
func (f Notification) CanSend() bool {
if f.SentLastHour() >= f.Limits {
return false
}
return true
}
func (f Notification) SentLastHour() int {
sent := 0
hourAgo := time.Now().Add(-1 * time.Hour)
for _, v := range f.Logs() {
lastTime := time.Time(v.Time)
if lastTime.After(hourAgo) {
sent++
}
}
return sent
}
func (f NotificationForm) Value() string {
notifier := Select(f.id)
return notifier.GetValue(f.DbField)
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 (n *Notification) GetValue(dbField string) string {
@ -144,6 +188,8 @@ func (n *Notification) GetValue(dbField string) string {
return n.ApiKey
case "api_secret":
return n.ApiSecret
case "limits":
return utils.IntString(int(n.Limits))
}
return ""
}

View File

@ -41,7 +41,7 @@ func init() {
Method: SLACK_METHOD,
Host: "https://webhooksurl.slack.com/***",
Form: []NotificationForm{{
id: 2,
Id: 2,
Type: "text",
Title: "Incoming Webhook Url",
Placeholder: "Insert your Slack webhook URL here.",
@ -84,11 +84,15 @@ func (u *Slack) Run() error {
messageLock.Lock()
slackMessages = uniqueStrings(slackMessages)
for _, msg := range slackMessages {
utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook"))
client := http.Client{Timeout: 15 * time.Second}
_, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg)))
if err != nil {
utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err))
if u.CanSend() {
utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook"))
client := http.Client{Timeout: 15 * time.Second}
_, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg)))
if err != nil {
utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err))
}
u.Log(msg)
}
}
slackMessages = []string{}
@ -146,7 +150,7 @@ func (u *Slack) OnSave() error {
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Slack) Install() error {
inDb, err := slacker.Notification.isInDatabase()
inDb, err := slacker.Notification.IsInDatabase()
if !inDb {
newNotifer, err := InsertDatabase(u.Notification)
if err != nil {

View File

@ -1,7 +1,7 @@
package plugin
import (
"net/http"
"github.com/hunterlong/statup/types"
"upper.io/db.v3/lib/sqlbuilder"
)
@ -21,48 +21,14 @@ var (
DB sqlbuilder.Database
)
func SetDatabase(database sqlbuilder.Database) {
DB = database
type PluginInfo struct {
i *types.Info
}
type PluginInfo struct {
Info Info
PluginActions
func SetDatabase(database sqlbuilder.Database) {
DB = database
}
func (p *PluginInfo) Form() string {
return "okkokokkok"
}
type PluginActions interface {
GetInfo() Info
GetForm() string
OnLoad(sqlbuilder.Database)
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 Routing struct {
URL string
Method string
Handler func(http.ResponseWriter, *http.Request)
}
type Info struct {
Name string
Description string
Form string
}

View File

@ -21,6 +21,11 @@ $(".confirm-btn").on('click', function() {
});
$(".select-input").on("click", function () {
$(this).select();
});
var ranVar = false;
var ranTheme = false;
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) {

View File

@ -59,26 +59,23 @@
{{ end }}
</div>
<h3>Latest Failures</h3>
{{ range .Services }}
{{ $s := .ToService }}
{{ if .LimitedFailures }}
<div class="list-group mt-3">
<h4>{{$s.Name}} Failures</h4>
<div class="list-group mt-3 mb-4">
{{ range .LimitedFailures }}
{{ $f := .ToFailure }}
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{.ParseError}}</h5>
<small>Reported {{.Ago}}</small>
</div>
<p class="mb-1">{{.Issue}}</p>
<p class="mb-1">{{$f.Issue}}</p>
</a>
{{ end }}
</div>
{{ end }}
{{ end }}

View File

@ -59,14 +59,15 @@
<canvas id="service" width="400" height="120"></canvas>
{{ if .LimitedFailures }}
<div class="list-group mt-5">
<div class="list-group mt-3 mb-4">
{{ range .LimitedFailures }}
{{ $f := .ToFailure }}
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{.ParseError}}</h5>
<small>Reported {{.Ago}}</small>
</div>
<p class="mb-1">{{.Issue}}</p>
<p class="mb-1">{{$f.Issue}}</p>
</a>
{{ end }}
</div>
@ -147,7 +148,7 @@
<button type="submit" class="btn btn-success btn-block">Update Service</button>
</div>
<div class="col-6">
<a href="/service/{{ $s.Id }}/delete_failures" class="btn btn-danger btn-block">Delete All Failures</a>
<a href="/service/{{ $s.Id }}/delete_failures" class="btn btn-danger btn-block confirm-btn">Delete All Failures</a>
</div>
</div>
</form>
@ -156,9 +157,7 @@
<div class="col-12 mt-4">
<h3>Last Response</h3>
<textarea rows="8" class="form-control" readonly>{{ $s.LastResponse }}</textarea>
<div class="form-group row mt-2">
<label for="last_status_code" class="col-sm-3 col-form-label">Status Code</label>
<div class="col-sm-2">

View File

@ -40,7 +40,7 @@
<td class="text-right">
<div class="btn-group">
<a href="/service/{{$s.Id}}" class="btn btn-primary">View</a>
<a href="/service/{{$s.Id}}/delete" class="btn btn-danger">Delete</a>
<a href="/service/{{$s.Id}}/delete" class="btn btn-danger confirm-btn">Delete</a>
</div>
</td>
</tr>
@ -91,6 +91,7 @@
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<textarea name="expected" class="form-control" id="service_response" rows="3"></textarea>
<small id="emailHelp" class="form-text text-muted">You can insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
</div>
</div>
<div class="form-group row">

View File

@ -20,7 +20,6 @@
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
{{template "nav"}}
<div class="col-12">
@ -79,14 +78,19 @@
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
<div class="form-group mt-3">
<label for="api_key">API Key</label>
<input type="text" class="form-control" value="{{ .ApiKey }}" id="api_key" readonly>
<div class="form-group row mt-3">
<label for="api_key" class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9">
<input type="text" class="form-control select-input" value="{{ .ApiKey }}" id="api_key" readonly>
</div>
</div>
<div class="form-group">
<label for="api_secret">API Secret</label>
<input type="text" class="form-control" value="{{ .ApiSecret }}" id="api_secret" readonly>
<div class="form-group row">
<label for="api_secret" class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9">
<input type="text" class="form-control select-input" value="{{ .ApiSecret }}" id="api_secret" readonly>
<small id="emailHelp" class="form-text text-muted">You can <a href="/api/renew">Regenerate API Keys</a> if you need to.</small>
</div>
</div>
</form>
@ -122,7 +126,7 @@
{{ range .Communications }}
<div class="tab-pane fade" id="v-pills-{{underscore .Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore .Method }}-tab">
<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 }}">
{{range .Form}}
<div class="form-group">
@ -130,6 +134,12 @@
<input type="{{.Type}}" name="{{underscore .DbField}}" class="form-control" value="{{ .Value }}" 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">
</div>
<div class="form-group row">
<div class="col-sm-6">
<span class="switch">
@ -143,6 +153,18 @@
</div>
</form>
{{ if .Logs }}
Sent {{.SentLastHour}} out of {{.LimitValue}} in the last hour<br>
{{ range .Logs }}
<div class="card mt-1">
<div class="card-body">
{{.Message}}
<p class="card-text"><small class="text-muted">Sent {{.Time.Ago}}</small></p>
</div>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}

View File

@ -1,9 +1,72 @@
package types
import (
"net/http"
"time"
"upper.io/db.v3/lib/sqlbuilder"
)
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(sqlbuilder.Database)
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{}
type Core struct {
Name string `db:"name" json:"name"`
Description string `db:"description" json:"name"`
Config string `db:"config" json:"-"`
ApiKey string `db:"api_key" json:"-"`
ApiSecret string `db:"api_secret" json:"-"`
Style string `db:"style" json:"-"`
Footer string `db:"footer" json:"-"`
Domain string `db:"domain" json:"domain,omitempty"`
Version string `db:"version" json:"version,omitempty"`
MigrationId int64 `db:"migration_id" json:"-"`
UseCdn bool `db:"use_cdn" json:"-"`
Services []*Service `json:"services,omitempty"`
Plugins []Info
Repos []PluginJSON
AllPlugins []PluginActions
Communications []AllNotifiers
DbConnection string
Started time.Time
}
type Service struct {
Id int64 `db:"id,omitempty" json:"id"`
Name string `db:"name" json:"name"`
@ -67,16 +130,6 @@ type Checkin struct {
Last time.Time `json:"last"`
}
type Email struct {
To string
Subject string
Template string
From string
Data interface{}
Source string
Sent bool
}
type Config struct {
Connection string `yaml:"connection"`
Host string `yaml:"host"`

View File

@ -1,10 +1,12 @@
package utils
import (
"github.com/ararog/timeago"
"os"
"regexp"
"strconv"
"strings"
"time"
)
func StringInt(s string) int64 {
@ -12,6 +14,21 @@ func StringInt(s string) int64 {
return int64(num)
}
func IntString(s int) string {
return strconv.Itoa(s)
}
type Timestamp time.Time
type Timestamper interface {
Ago() string
}
func (t Timestamp) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), time.Time(t))
return got
}
func UnderScoreString(str string) string {
// convert every letter to lower case