mirror of https://github.com/statping/statping
commit
d0939339cd
|
@ -1,5 +1,5 @@
|
|||
.idea
|
||||
rice-box.go
|
||||
./rice-box.go
|
||||
config.yml
|
||||
statup.db
|
||||
plugins/*.so
|
||||
|
|
|
@ -18,7 +18,7 @@ services:
|
|||
|
||||
env:
|
||||
global:
|
||||
- VERSION=0.29.9
|
||||
- VERSION=0.30
|
||||
- DB_HOST=localhost
|
||||
- DB_USER=travis
|
||||
- DB_PASS=
|
||||
|
|
48
cli.go
48
cli.go
|
@ -4,7 +4,7 @@ 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"
|
||||
"io/ioutil"
|
||||
|
@ -106,7 +106,7 @@ func RunOnce() {
|
|||
utils.Log(4, err)
|
||||
}
|
||||
for _, s := range core.CoreApp.Services {
|
||||
out := s.Check()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -127,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()
|
||||
|
||||
|
@ -148,41 +148,41 @@ func TestPlugin(plug plugin.PluginActions) {
|
|||
core.OnLoad(core.DbSession)
|
||||
fmt.Println("\n" + BRAKER)
|
||||
fmt.Println(POINT + "Sending 'OnSuccess(Service)'")
|
||||
core.OnSuccess(core.SelectService(1))
|
||||
core.OnSuccess(core.SelectService(1).ToService())
|
||||
fmt.Println("\n" + BRAKER)
|
||||
fmt.Println(POINT + "Sending 'OnFailure(Service, FailureData)'")
|
||||
fakeFailD := core.FailureData{
|
||||
Issue: "No issue, just testing this plugin. This would include HTTP failure information though",
|
||||
}
|
||||
core.OnFailure(core.SelectService(1), fakeFailD)
|
||||
core.OnFailure(core.SelectService(1).ToService(), fakeFailD)
|
||||
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))
|
||||
core.OnNewService(core.SelectService(2).ToService())
|
||||
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 := core.SelectService(2).ToService()
|
||||
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))
|
||||
core.OnDeletedService(core.SelectService(1).ToService())
|
||||
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)
|
||||
|
||||
|
@ -212,21 +212,21 @@ func FakeSeed(plug plugin.PluginActions) {
|
|||
core.CoreApp.ApiSecret = "0x0x0x0x0"
|
||||
core.CoreApp.ApiKey = "abcdefg12345"
|
||||
|
||||
fakeSrv := &core.Service{
|
||||
fakeSrv := &types.Service{
|
||||
Name: "Test Plugin Service",
|
||||
Domain: "https://google.com",
|
||||
Method: "GET",
|
||||
}
|
||||
fakeSrv.Create()
|
||||
core.CreateService(fakeSrv)
|
||||
|
||||
fakeSrv2 := &core.Service{
|
||||
fakeSrv2 := &types.Service{
|
||||
Name: "Awesome Plugin Service",
|
||||
Domain: "https://netflix.com",
|
||||
Method: "GET",
|
||||
}
|
||||
fakeSrv2.Create()
|
||||
core.CreateService(fakeSrv2)
|
||||
|
||||
fakeUser := &core.User{
|
||||
fakeUser := &types.User{
|
||||
Id: 6334,
|
||||
Username: "Bulbasaur",
|
||||
Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
|
||||
|
@ -234,35 +234,37 @@ func FakeSeed(plug plugin.PluginActions) {
|
|||
Admin: true,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
fakeUser.Create()
|
||||
core.CreateUser(fakeUser)
|
||||
|
||||
fakeUser = &core.User{
|
||||
fakeUser = &types.User{
|
||||
Id: 6335,
|
||||
Username: "Billy",
|
||||
Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
|
||||
Email: "info@awesome.com",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
fakeUser.Create()
|
||||
core.CreateUser(fakeUser)
|
||||
|
||||
for i := 0; i <= 50; i++ {
|
||||
dd := core.HitData{
|
||||
Latency: rand.Float64(),
|
||||
}
|
||||
fakeSrv.CreateHit(dd)
|
||||
core.CreateServiceHit(fakeSrv, dd)
|
||||
|
||||
dd = core.HitData{
|
||||
Latency: rand.Float64(),
|
||||
}
|
||||
fakeSrv2.CreateHit(dd)
|
||||
core.CreateServiceHit(fakeSrv2, dd)
|
||||
|
||||
fail := core.FailureData{
|
||||
Issue: "This is not an issue, but it would container HTTP response errors.",
|
||||
}
|
||||
fakeSrv.CreateFailure(fail)
|
||||
core.CreateServiceFailure(fakeSrv, fail)
|
||||
|
||||
fail = core.FailureData{
|
||||
Issue: "HTTP Status Code 521 did not match 200",
|
||||
}
|
||||
fakeSrv2.CreateFailure(fail)
|
||||
core.CreateServiceFailure(fakeSrv, fail)
|
||||
}
|
||||
|
||||
fmt.Println("Seeding example data is complete, running Plugin Tests")
|
||||
|
|
|
@ -95,8 +95,8 @@ func CreateAllAssets() {
|
|||
utils.Log(1, "Inserting scss, css, emails, and javascript files into assets..")
|
||||
CopyToPublic(ScssBox, "scss", "base.scss")
|
||||
CopyToPublic(ScssBox, "scss", "variables.scss")
|
||||
CopyToPublic(EmailBox, "emails", "message.html")
|
||||
CopyToPublic(EmailBox, "emails", "failure.html")
|
||||
//CopyToPublic(EmailBox, "emails", "message.html")
|
||||
//CopyToPublic(EmailBox, "emails", "failure.html")
|
||||
CopyToPublic(CssBox, "css", "bootstrap.min.css")
|
||||
CopyToPublic(JsBox, "js", "bootstrap.min.js")
|
||||
CopyToPublic(JsBox, "js", "Chart.bundle.min.js")
|
||||
|
|
|
@ -18,21 +18,21 @@ type FailureData types.FailureData
|
|||
func CheckServices() {
|
||||
CoreApp.Services, _ = SelectAllServices()
|
||||
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
|
||||
for _, v := range CoreApp.Services {
|
||||
obj := v
|
||||
for _, ser := range CoreApp.Services {
|
||||
s := ser.ToService()
|
||||
//go obj.StartCheckins()
|
||||
obj.stopRoutine = make(chan struct{})
|
||||
go obj.CheckQueue()
|
||||
s.StopRoutine = make(chan struct{})
|
||||
go CheckQueue(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CheckQueue() {
|
||||
func CheckQueue(s *types.Service) {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopRoutine:
|
||||
case <-s.StopRoutine:
|
||||
return
|
||||
default:
|
||||
s.Check()
|
||||
ServiceCheck(s)
|
||||
if s.Interval < 1 {
|
||||
s.Interval = 1
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func (s *Service) CheckQueue() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) DNSCheck() (float64, error) {
|
||||
func DNSCheck(s *types.Service) (float64, error) {
|
||||
t1 := time.Now()
|
||||
url, err := url.Parse(s.Domain)
|
||||
if err != nil {
|
||||
|
@ -58,13 +58,13 @@ func (s *Service) DNSCheck() (float64, error) {
|
|||
return subTime, err
|
||||
}
|
||||
|
||||
func (s *Service) Check() *Service {
|
||||
dnsLookup, err := s.DNSCheck()
|
||||
func ServiceCheck(s *types.Service) *types.Service {
|
||||
dnsLookup, err := DNSCheck(s)
|
||||
if err != nil {
|
||||
s.Failure(fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err))
|
||||
RecordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err))
|
||||
return s
|
||||
}
|
||||
s.dnsLookup = dnsLookup
|
||||
s.DnsLookup = dnsLookup
|
||||
t1 := time.Now()
|
||||
client := http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
|
@ -77,14 +77,14 @@ func (s *Service) Check() *Service {
|
|||
response, err = client.Get(s.Domain)
|
||||
}
|
||||
if err != nil {
|
||||
s.Failure(fmt.Sprintf("HTTP Error %v", err))
|
||||
RecordFailure(s, fmt.Sprintf("HTTP Error %v", err))
|
||||
return s
|
||||
}
|
||||
response.Header.Set("User-Agent", "StatupMonitor")
|
||||
t2 := time.Now()
|
||||
s.Latency = t2.Sub(t1).Seconds()
|
||||
if err != nil {
|
||||
s.Failure(fmt.Sprintf("HTTP Error %v", err))
|
||||
RecordFailure(s, fmt.Sprintf("HTTP Error %v", err))
|
||||
return s
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
@ -100,20 +100,20 @@ func (s *Service) Check() *Service {
|
|||
if !match {
|
||||
s.LastResponse = string(contents)
|
||||
s.LastStatusCode = response.StatusCode
|
||||
s.Failure(fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
|
||||
RecordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
|
||||
return s
|
||||
}
|
||||
}
|
||||
if s.ExpectedStatus != response.StatusCode {
|
||||
s.LastResponse = string(contents)
|
||||
s.LastStatusCode = response.StatusCode
|
||||
s.Failure(fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus))
|
||||
RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus))
|
||||
return s
|
||||
}
|
||||
s.LastResponse = string(contents)
|
||||
s.LastStatusCode = response.StatusCode
|
||||
s.Online = true
|
||||
s.Record(response)
|
||||
RecordSuccess(s, response)
|
||||
return s
|
||||
}
|
||||
|
||||
|
@ -121,23 +121,23 @@ type HitData struct {
|
|||
Latency float64
|
||||
}
|
||||
|
||||
func (s *Service) Record(response *http.Response) {
|
||||
func RecordSuccess(s *types.Service, response *http.Response) {
|
||||
s.Online = true
|
||||
s.LastOnline = time.Now()
|
||||
data := HitData{
|
||||
Latency: s.Latency,
|
||||
}
|
||||
s.CreateHit(data)
|
||||
CreateServiceHit(s, data)
|
||||
OnSuccess(s)
|
||||
}
|
||||
|
||||
func (s *Service) Failure(issue string) {
|
||||
func RecordFailure(s *types.Service, issue string) {
|
||||
s.Online = false
|
||||
data := FailureData{
|
||||
Issue: issue,
|
||||
}
|
||||
utils.Log(2, fmt.Sprintf("Service %v Failing: %v", s.Name, issue))
|
||||
s.CreateFailure(data)
|
||||
CreateServiceFailure(s, data)
|
||||
//SendFailureEmail(s)
|
||||
OnFailure(s, data)
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ func (c *Checkin) String() string {
|
|||
return c.Api
|
||||
}
|
||||
|
||||
func FindCheckin(api string) *Checkin {
|
||||
for _, s := range CoreApp.Services {
|
||||
for _, c := range s.Checkins {
|
||||
func FindCheckin(api string) *types.Checkin {
|
||||
for _, ser := range CoreApp.Services {
|
||||
for _, c := range ser.ToService().Checkins {
|
||||
if c.Api == api {
|
||||
return c
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ func FindCheckin(api string) *Checkin {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SelectAllCheckins() []*Checkin {
|
||||
var checkins []*Checkin
|
||||
func SelectAllCheckins(s *types.Service) []*types.Checkin {
|
||||
var checkins []*types.Checkin
|
||||
col := DbSession.Collection("checkins").Find("service", s.Id).OrderBy("-id")
|
||||
col.All(&checkins)
|
||||
s.Checkins = checkins
|
||||
|
@ -78,33 +78,33 @@ func (f *Checkin) Ago() string {
|
|||
return got
|
||||
}
|
||||
|
||||
func (c *Checkin) Run() {
|
||||
if c.Interval == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Println("checking: ", c.Api)
|
||||
between := time.Now().Sub(c.Last).Seconds()
|
||||
if between > float64(c.Interval) {
|
||||
guard := make(chan struct{})
|
||||
c.RecheckCheckinFailure(guard)
|
||||
<-guard
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
c.Run()
|
||||
}
|
||||
|
||||
func (s *Service) StartCheckins() {
|
||||
for _, c := range s.Checkins {
|
||||
checkin := c
|
||||
go checkin.Run()
|
||||
}
|
||||
}
|
||||
|
||||
func CheckinProcess() {
|
||||
for _, s := range CoreApp.Services {
|
||||
for _, c := range s.Checkins {
|
||||
checkin := c
|
||||
go checkin.Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
//func (c *Checkin) Run() {
|
||||
// if c.Interval == 0 {
|
||||
// return
|
||||
// }
|
||||
// fmt.Println("checking: ", c.Api)
|
||||
// between := time.Now().Sub(c.Last).Seconds()
|
||||
// if between > float64(c.Interval) {
|
||||
// guard := make(chan struct{})
|
||||
// c.RecheckCheckinFailure(guard)
|
||||
// <-guard
|
||||
// }
|
||||
// time.Sleep(1 * time.Second)
|
||||
// c.Run()
|
||||
//}
|
||||
//
|
||||
//func (s *Service) StartCheckins() {
|
||||
// for _, c := range s.Checkins {
|
||||
// checkin := c.(*Checkin)
|
||||
// go checkin.Run()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func CheckinProcess() {
|
||||
// for _, s := range CoreApp.Services {
|
||||
// for _, c := range s.Checkins {
|
||||
// checkin := c
|
||||
// go checkin.Run()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
func LoadConfig() (*types.Config, error) {
|
||||
if os.Getenv("DB_CONN") != "" {
|
||||
utils.Log(1, "DB_CONN environment variable was found, sleeping for 30 seconds")
|
||||
time.Sleep(30 * time.Second)
|
||||
//time.Sleep(30 * time.Second)
|
||||
return LoadUsingEnv()
|
||||
}
|
||||
Configs = new(types.Config)
|
||||
|
@ -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,22 +93,22 @@ 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)
|
||||
}
|
||||
|
||||
admin := &User{
|
||||
admin := &types.User{
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
Email: "info@admin.com",
|
||||
Admin: true,
|
||||
}
|
||||
admin.Create()
|
||||
CreateUser(admin)
|
||||
|
||||
LoadSampleData()
|
||||
|
||||
|
|
47
core/core.go
47
core/core.go
|
@ -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 (
|
||||
|
@ -42,7 +25,6 @@ var (
|
|||
ScssBox *rice.Box
|
||||
JsBox *rice.Box
|
||||
TmplBox *rice.Box
|
||||
EmailBox *rice.Box
|
||||
SetupMode bool
|
||||
UsingAssets bool
|
||||
VERSION string
|
||||
|
@ -54,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")
|
||||
|
@ -73,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
|
||||
}
|
||||
|
||||
|
@ -105,7 +92,8 @@ func (c Core) MobileSASS() string {
|
|||
}
|
||||
|
||||
func (c Core) AllOnline() bool {
|
||||
for _, s := range CoreApp.Services {
|
||||
for _, ser := range CoreApp.Services {
|
||||
s := ser.ToService()
|
||||
if !s.Online {
|
||||
return false
|
||||
}
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ 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"
|
||||
)
|
||||
|
||||
|
@ -13,55 +13,55 @@ func OnLoad(db sqlbuilder.Database) {
|
|||
}
|
||||
}
|
||||
|
||||
func OnSuccess(s *Service) {
|
||||
func OnSuccess(s *types.Service) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnSuccess(structs.Map(s))
|
||||
}
|
||||
notifiers.OnSuccess()
|
||||
notifiers.OnSuccess(s)
|
||||
}
|
||||
|
||||
func OnFailure(s *Service, f FailureData) {
|
||||
func OnFailure(s *types.Service, f FailureData) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnFailure(structs.Map(s))
|
||||
}
|
||||
notifiers.OnFailure()
|
||||
notifiers.OnFailure(s)
|
||||
}
|
||||
|
||||
func OnSettingsSaved(c *Core) {
|
||||
func OnSettingsSaved(c *types.Core) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnSettingsSaved(structs.Map(c))
|
||||
}
|
||||
}
|
||||
|
||||
func OnNewUser(u *User) {
|
||||
func OnNewUser(u *types.User) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnNewUser(structs.Map(u))
|
||||
}
|
||||
}
|
||||
|
||||
func OnNewService(s *Service) {
|
||||
func OnNewService(s *types.Service) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnNewService(structs.Map(s))
|
||||
}
|
||||
}
|
||||
|
||||
func OnDeletedService(s *Service) {
|
||||
func OnDeletedService(s *types.Service) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnDeletedService(structs.Map(s))
|
||||
}
|
||||
}
|
||||
|
||||
func OnUpdateService(s *Service) {
|
||||
func OnUpdateService(s *types.Service) {
|
||||
for _, p := range CoreApp.AllPlugins {
|
||||
p.OnUpdatedService(structs.Map(s))
|
||||
}
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *Service) CreateFailure(data FailureData) (int64, error) {
|
||||
fail := &Failure{
|
||||
func CreateServiceFailure(s *types.Service, data FailureData) (int64, error) {
|
||||
fail := &types.Failure{
|
||||
Issue: data.Issue,
|
||||
Service: s.Id,
|
||||
CreatedAt: time.Now(),
|
||||
|
@ -26,8 +27,8 @@ func (s *Service) CreateFailure(data FailureData) (int64, error) {
|
|||
return uuid.(int64), err
|
||||
}
|
||||
|
||||
func (s *Service) SelectAllFailures() []*Failure {
|
||||
var fails []*Failure
|
||||
func SelectAllFailures(s *types.Service) []*types.Failure {
|
||||
var fails []*types.Failure
|
||||
col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id")
|
||||
err := col.All(&fails)
|
||||
if err != nil {
|
||||
|
@ -36,7 +37,7 @@ func (s *Service) SelectAllFailures() []*Failure {
|
|||
return fails
|
||||
}
|
||||
|
||||
func (u *Service) DeleteFailures() {
|
||||
func DeleteFailures(u *types.Service) {
|
||||
var fails []*Failure
|
||||
col := DbSession.Collection("failures")
|
||||
col.Find("service", u.Id).All(&fails)
|
||||
|
@ -45,26 +46,33 @@ func (u *Service) DeleteFailures() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) LimitedFailures() []*Failure {
|
||||
var fails []*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 []*Failure) []*Failure {
|
||||
func reverseFailures(input []*types.Failure) []*types.Failure {
|
||||
if len(input) == 0 {
|
||||
return input
|
||||
}
|
||||
return append(reverseFailures(input[1:]), input[0])
|
||||
}
|
||||
|
||||
func (f *Failure) Ago() string {
|
||||
func (fail *Failure) Ago() string {
|
||||
f := fail.ToFailure()
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
|
||||
return got
|
||||
}
|
||||
|
||||
func (f *Failure) Delete() error {
|
||||
func (fail *Failure) Delete() error {
|
||||
f := fail.ToFailure()
|
||||
col := DbSession.Collection("failures").Find("id", f.Id)
|
||||
return col.Delete()
|
||||
}
|
||||
|
@ -79,19 +87,31 @@ func CountFailures() uint64 {
|
|||
return amount
|
||||
}
|
||||
|
||||
func (s *Service) TotalFailures() (uint64, error) {
|
||||
func (ser *Service) TotalFailures() (uint64, error) {
|
||||
s := ser.ToService()
|
||||
col := DbSession.Collection("failures").Find("service", s.Id)
|
||||
amount, err := col.Count()
|
||||
return amount, err
|
||||
}
|
||||
|
||||
func (s *Service) TotalFailures24Hours() (uint64, error) {
|
||||
func (ser *Service) TotalFailures24Hours() (uint64, error) {
|
||||
s := ser.ToService()
|
||||
col := DbSession.Collection("failures").Find("service", s.Id)
|
||||
amount, err := col.Count()
|
||||
return amount, err
|
||||
}
|
||||
|
||||
func (f *Failure) ParseError() string {
|
||||
func (f *Failure) ToFailure() *types.Failure {
|
||||
return f.F.(*types.Failure)
|
||||
}
|
||||
|
||||
func MakeFailure(f *types.Failure) *Failure {
|
||||
fail := &Failure{f}
|
||||
return fail
|
||||
}
|
||||
|
||||
func (fail *Failure) ParseError() string {
|
||||
f := fail.ToFailure()
|
||||
err := strings.Contains(f.Issue, "operation timed out")
|
||||
if err {
|
||||
return fmt.Sprintf("HTTP Request Timed Out")
|
||||
|
|
14
core/hits.go
14
core/hits.go
|
@ -13,7 +13,7 @@ func hitCol() db.Collection {
|
|||
return DbSession.Collection("hits")
|
||||
}
|
||||
|
||||
func (s *Service) CreateHit(d HitData) (int64, error) {
|
||||
func CreateServiceHit(s *types.Service, d HitData) (int64, error) {
|
||||
h := Hit{
|
||||
Service: s.Id,
|
||||
Latency: d.Latency,
|
||||
|
@ -27,14 +27,16 @@ func (s *Service) CreateHit(d HitData) (int64, error) {
|
|||
return uuid.(int64), err
|
||||
}
|
||||
|
||||
func (s *Service) Hits() ([]Hit, error) {
|
||||
func (ser *Service) Hits() ([]Hit, error) {
|
||||
s := ser.ToService()
|
||||
var hits []Hit
|
||||
col := hitCol().Find("service", s.Id).OrderBy("-id")
|
||||
err := col.All(&hits)
|
||||
return hits, err
|
||||
}
|
||||
|
||||
func (s *Service) LimitedHits() ([]*Hit, error) {
|
||||
func (ser *Service) LimitedHits() ([]*Hit, error) {
|
||||
s := ser.ToService()
|
||||
var hits []*Hit
|
||||
col := hitCol().Find("service", s.Id).OrderBy("-id").Limit(1024)
|
||||
err := col.All(&hits)
|
||||
|
@ -48,14 +50,16 @@ func reverseHits(input []*Hit) []*Hit {
|
|||
return append(reverseHits(input[1:]), input[0])
|
||||
}
|
||||
|
||||
func (s *Service) SelectHitsGroupBy(group string) ([]Hit, error) {
|
||||
func (ser *Service) SelectHitsGroupBy(group string) ([]Hit, error) {
|
||||
s := ser.ToService()
|
||||
var hits []Hit
|
||||
col := hitCol().Find("service", s.Id)
|
||||
err := col.All(&hits)
|
||||
return hits, err
|
||||
}
|
||||
|
||||
func (s *Service) TotalHits() (uint64, error) {
|
||||
func (ser *Service) TotalHits() (uint64, error) {
|
||||
s := ser.ToService()
|
||||
col := hitCol().Find("service", s.Id)
|
||||
amount, err := col.Count()
|
||||
return amount, err
|
||||
|
|
119
core/services.go
119
core/services.go
|
@ -10,33 +10,12 @@ import (
|
|||
"upper.io/db.v3"
|
||||
)
|
||||
|
||||
type Failure types.Failure
|
||||
|
||||
type Service struct {
|
||||
Id int64 `db:"id,omitempty" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Domain string `db:"domain" json:"domain"`
|
||||
Expected string `db:"expected" json:"expected"`
|
||||
ExpectedStatus int `db:"expected_status" json:"expected_status"`
|
||||
Interval int `db:"check_interval" json:"check_interval"`
|
||||
Type string `db:"check_type" json:"type"`
|
||||
Method string `db:"method" json:"method"`
|
||||
PostData string `db:"post_data" json:"post_data"`
|
||||
Port int `db:"port" json:"port"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Online bool `json:"online"`
|
||||
Latency float64 `json:"latency"`
|
||||
Online24Hours float32 `json:"24_hours_online"`
|
||||
AvgResponse string `json:"avg_response"`
|
||||
TotalUptime string `json:"uptime"`
|
||||
OrderId int64 `json:"order_id"`
|
||||
Failures []*Failure `json:"failures"`
|
||||
Checkins []*Checkin `json:"checkins"`
|
||||
stopRoutine chan struct{}
|
||||
LastResponse string
|
||||
LastStatusCode int
|
||||
LastOnline time.Time
|
||||
dnsLookup float64 `json:"dns_lookup_time"`
|
||||
s *types.Service
|
||||
}
|
||||
|
||||
type Failure struct {
|
||||
F interface{}
|
||||
}
|
||||
|
||||
func serviceCol() db.Collection {
|
||||
|
@ -45,27 +24,31 @@ func serviceCol() db.Collection {
|
|||
|
||||
func SelectService(id int64) *Service {
|
||||
for _, s := range CoreApp.Services {
|
||||
if s.Id == id {
|
||||
return s
|
||||
ser := s.ToService()
|
||||
if ser.Id == id {
|
||||
return &Service{ser}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SelectAllServices() ([]*Service, error) {
|
||||
var srvcs []*Service
|
||||
var services []*types.Service
|
||||
var sers []*Service
|
||||
col := serviceCol().Find()
|
||||
err := col.All(&srvcs)
|
||||
err := col.All(&services)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, s := range srvcs {
|
||||
s.Checkins = s.SelectAllCheckins()
|
||||
s.Failures = s.SelectAllFailures()
|
||||
for _, s := range services {
|
||||
ser := NewService(s)
|
||||
sers = append(sers, ser)
|
||||
s.Checkins = SelectAllCheckins(s)
|
||||
s.Failures = SelectAllFailures(s)
|
||||
}
|
||||
CoreApp.Services = srvcs
|
||||
return srvcs, err
|
||||
CoreApp.Services = sers
|
||||
return sers, err
|
||||
}
|
||||
|
||||
func (s *Service) AvgTime() float64 {
|
||||
|
@ -80,9 +63,10 @@ func (s *Service) AvgTime() float64 {
|
|||
return val
|
||||
}
|
||||
|
||||
func (s *Service) Online24() float32 {
|
||||
total, _ := s.TotalHits()
|
||||
failed, _ := s.TotalFailures24Hours()
|
||||
func (ser *Service) Online24() float32 {
|
||||
s := ser.ToService()
|
||||
total, _ := ser.TotalHits()
|
||||
failed, _ := ser.TotalFailures24Hours()
|
||||
if failed == 0 {
|
||||
s.Online24Hours = 100.00
|
||||
return s.Online24Hours
|
||||
|
@ -106,12 +90,22 @@ type DateScan struct {
|
|||
Value int64 `json:"y"`
|
||||
}
|
||||
|
||||
func (s *Service) SmallText() string {
|
||||
last := s.LimitedFailures()
|
||||
hits, _ := s.LimitedHits()
|
||||
func (s *Service) ToService() *types.Service {
|
||||
return s.s
|
||||
}
|
||||
|
||||
func NewService(s *types.Service) *Service {
|
||||
return &Service{s}
|
||||
}
|
||||
|
||||
func (ser *Service) SmallText() string {
|
||||
s := ser.ToService()
|
||||
last := ser.LimitedFailures()
|
||||
hits, _ := ser.LimitedHits()
|
||||
if !s.Online {
|
||||
if len(last) > 0 {
|
||||
return fmt.Sprintf("%v on %v", last[0].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)
|
||||
}
|
||||
|
@ -138,7 +132,8 @@ func GroupDataBy(column string, id int64, tm time.Time, increment string) string
|
|||
return sql
|
||||
}
|
||||
|
||||
func (s *Service) GraphData() string {
|
||||
func (ser *Service) GraphData() string {
|
||||
s := ser.ToService()
|
||||
var d []*DateScan
|
||||
since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0)
|
||||
|
||||
|
@ -172,9 +167,10 @@ func (s *Service) GraphData() string {
|
|||
return string(data)
|
||||
}
|
||||
|
||||
func (s *Service) AvgUptime() string {
|
||||
failed, _ := s.TotalFailures()
|
||||
total, _ := s.TotalHits()
|
||||
func (ser *Service) AvgUptime() string {
|
||||
s := ser.ToService()
|
||||
failed, _ := ser.TotalFailures()
|
||||
total, _ := ser.TotalHits()
|
||||
if failed == 0 {
|
||||
s.TotalUptime = "100"
|
||||
return s.TotalUptime
|
||||
|
@ -195,10 +191,11 @@ func (s *Service) AvgUptime() string {
|
|||
return s.TotalUptime
|
||||
}
|
||||
|
||||
func (u *Service) RemoveArray() []*Service {
|
||||
func RemoveArray(u *types.Service) []*Service {
|
||||
var srvcs []*Service
|
||||
for _, s := range CoreApp.Services {
|
||||
if s.Id != u.Id {
|
||||
ser := s.ToService()
|
||||
if ser.Id != u.Id {
|
||||
srvcs = append(srvcs, s)
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +203,7 @@ func (u *Service) RemoveArray() []*Service {
|
|||
return srvcs
|
||||
}
|
||||
|
||||
func (u *Service) Delete() error {
|
||||
func DeleteService(u *types.Service) error {
|
||||
res := serviceCol().Find("id", u.Id)
|
||||
err := res.Delete()
|
||||
if err != nil {
|
||||
|
@ -214,28 +211,27 @@ func (u *Service) Delete() error {
|
|||
return err
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Stopping %v Monitoring...", u.Name))
|
||||
if u.stopRoutine != nil {
|
||||
close(u.stopRoutine)
|
||||
if u.StopRoutine != nil {
|
||||
close(u.StopRoutine)
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Stopped %v Monitoring Service", u.Name))
|
||||
u.RemoveArray()
|
||||
RemoveArray(u)
|
||||
OnDeletedService(u)
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *Service) Update(s *Service) *Service {
|
||||
s.CreatedAt = time.Now()
|
||||
func UpdateService(u *types.Service) *types.Service {
|
||||
u.CreatedAt = time.Now()
|
||||
res := serviceCol().Find("id", u.Id)
|
||||
err := res.Update(s)
|
||||
err := res.Update(u)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", u.Name, err))
|
||||
}
|
||||
*u = *s
|
||||
OnUpdateService(u)
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *Service) Create() (int64, error) {
|
||||
func CreateService(u *types.Service) (int64, error) {
|
||||
u.CreatedAt = time.Now()
|
||||
uuid, err := serviceCol().Insert(u)
|
||||
if uuid == nil {
|
||||
|
@ -243,15 +239,16 @@ func (u *Service) Create() (int64, error) {
|
|||
return 0, err
|
||||
}
|
||||
u.Id = uuid.(int64)
|
||||
u.stopRoutine = make(chan struct{})
|
||||
CoreApp.Services = append(CoreApp.Services, u)
|
||||
u.StopRoutine = make(chan struct{})
|
||||
CoreApp.Services = append(CoreApp.Services, &Service{u})
|
||||
return uuid.(int64), err
|
||||
}
|
||||
|
||||
func CountOnline() int {
|
||||
amount := 0
|
||||
for _, v := range CoreApp.Services {
|
||||
if v.Online {
|
||||
for _, s := range CoreApp.Services {
|
||||
ser := s.ToService()
|
||||
if ser.Online {
|
||||
amount++
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"os"
|
||||
)
|
||||
|
@ -19,7 +20,7 @@ type ErrorResponse struct {
|
|||
|
||||
func LoadSampleData() error {
|
||||
utils.Log(1, "Inserting Sample Data...")
|
||||
s1 := &Service{
|
||||
s1 := &types.Service{
|
||||
Name: "Google",
|
||||
Domain: "https://google.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -28,7 +29,7 @@ func LoadSampleData() error {
|
|||
Type: "http",
|
||||
Method: "GET",
|
||||
}
|
||||
s2 := &Service{
|
||||
s2 := &types.Service{
|
||||
Name: "Statup Github",
|
||||
Domain: "https://github.com/hunterlong/statup",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -37,7 +38,7 @@ func LoadSampleData() error {
|
|||
Type: "http",
|
||||
Method: "GET",
|
||||
}
|
||||
s3 := &Service{
|
||||
s3 := &types.Service{
|
||||
Name: "JSON Users Test",
|
||||
Domain: "https://jsonplaceholder.typicode.com/users",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -46,7 +47,7 @@ func LoadSampleData() error {
|
|||
Type: "http",
|
||||
Method: "GET",
|
||||
}
|
||||
s4 := &Service{
|
||||
s4 := &types.Service{
|
||||
Name: "JSON API Tester",
|
||||
Domain: "https://jsonplaceholder.typicode.com/posts",
|
||||
ExpectedStatus: 201,
|
||||
|
@ -56,19 +57,19 @@ func LoadSampleData() error {
|
|||
Method: "POST",
|
||||
PostData: `{ "title": "statup", "body": "bar", "userId": 19999 }`,
|
||||
}
|
||||
id, err := s1.Create()
|
||||
id, err := CreateService(s1)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err))
|
||||
}
|
||||
id, err = s2.Create()
|
||||
id, err = CreateService(s2)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err))
|
||||
}
|
||||
id, err = s3.Create()
|
||||
id, err = CreateService(s3)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err))
|
||||
}
|
||||
id, err = s4.Create()
|
||||
id, err = CreateService(s4)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err))
|
||||
}
|
||||
|
|
|
@ -10,36 +10,36 @@ import (
|
|||
|
||||
type User types.User
|
||||
|
||||
func SelectUser(id int64) (*User, error) {
|
||||
var user User
|
||||
func SelectUser(id int64) (*types.User, error) {
|
||||
var user *types.User
|
||||
col := DbSession.Collection("users")
|
||||
res := col.Find("id", id)
|
||||
err := res.One(&user)
|
||||
return &user, err
|
||||
return user, err
|
||||
}
|
||||
|
||||
func SelectUsername(username string) (*User, error) {
|
||||
var user User
|
||||
func SelectUsername(username string) (*types.User, error) {
|
||||
var user *types.User
|
||||
col := DbSession.Collection("users")
|
||||
res := col.Find("username", username)
|
||||
err := res.One(&user)
|
||||
return &user, err
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (u *User) Delete() error {
|
||||
func DeleteUser(u *types.User) error {
|
||||
col := DbSession.Collection("users")
|
||||
user := col.Find("id", u.Id)
|
||||
return user.Delete()
|
||||
}
|
||||
|
||||
func (u *User) Update() error {
|
||||
func UpdateUser(u *types.User) error {
|
||||
u.CreatedAt = time.Now()
|
||||
col := DbSession.Collection("users")
|
||||
user := col.Find("id", u.Id)
|
||||
return user.Update(u)
|
||||
}
|
||||
|
||||
func (u *User) Create() (int64, error) {
|
||||
func CreateUser(u *types.User) (int64, error) {
|
||||
u.CreatedAt = time.Now()
|
||||
u.Password = utils.HashPassword(u.Password)
|
||||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
|
@ -63,7 +63,7 @@ func SelectAllUsers() ([]User, error) {
|
|||
return users, err
|
||||
}
|
||||
|
||||
func AuthUser(username, password string) (*User, bool) {
|
||||
func AuthUser(username, password string) (*types.User, bool) {
|
||||
var auth bool
|
||||
user, err := SelectUsername(username)
|
||||
if err != nil {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statup/core"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -16,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)
|
||||
|
@ -23,7 +39,7 @@ func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
vars := mux.Vars(r)
|
||||
checkin := core.FindCheckin(vars["api"])
|
||||
checkin.Receivehit()
|
||||
//checkin.Receivehit()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(checkin)
|
||||
}
|
||||
|
@ -44,11 +60,12 @@ func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
var s core.Service
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
var s *types.Service
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.Decode(&s)
|
||||
service.Update(&s)
|
||||
service := serv.ToService()
|
||||
core.UpdateService(service)
|
||||
json.NewEncoder(w).Encode(s)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@ import (
|
|||
)
|
||||
|
||||
type index struct {
|
||||
Core core.Core
|
||||
Services []*core.Service
|
||||
Core *core.Core
|
||||
}
|
||||
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -15,6 +14,5 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, "/setup", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
out := index{*core.CoreApp, core.CoreApp.Services}
|
||||
ExecuteResponse(w, r, "index.html", out)
|
||||
ExecuteResponse(w, r, "index.html", core.CoreApp)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ func PrometheusHandler(w http.ResponseWriter, r *http.Request) {
|
|||
system := fmt.Sprintf("statup_total_failures %v\n", core.CountFailures())
|
||||
system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services))
|
||||
metrics = append(metrics, system)
|
||||
for _, v := range core.CoreApp.Services {
|
||||
for _, ser := range core.CoreApp.Services {
|
||||
v := ser.ToService()
|
||||
online := 1
|
||||
if !v.Online {
|
||||
online = 0
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statup/core"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
@ -40,7 +41,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
checkType := r.PostForm.Get("check_type")
|
||||
postData := r.PostForm.Get("post_data")
|
||||
|
||||
service := &core.Service{
|
||||
service := &types.Service{
|
||||
Name: name,
|
||||
Domain: domain,
|
||||
Method: method,
|
||||
|
@ -51,12 +52,12 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
Port: port,
|
||||
PostData: postData,
|
||||
}
|
||||
_, err := service.Create()
|
||||
_, err := core.CreateService(service)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Error starting %v check routine. %v", service.Name, err))
|
||||
}
|
||||
|
||||
go service.CheckQueue()
|
||||
go core.CheckQueue(service)
|
||||
core.OnNewService(service)
|
||||
|
||||
http.Redirect(w, r, "/services", http.StatusSeeOther)
|
||||
|
@ -68,21 +69,25 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
service.Delete()
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
service := serv.ToService()
|
||||
core.DeleteService(service)
|
||||
http.Redirect(w, r, "/services", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
ExecuteResponse(w, r, "service.html", service)
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
|
||||
fmt.Println(serv.ToService())
|
||||
|
||||
ExecuteResponse(w, r, "service.html", serv)
|
||||
}
|
||||
|
||||
func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
service := serv.ToService()
|
||||
var badge []byte
|
||||
if service.Online {
|
||||
badge = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="104" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h50v20H54z"/><path fill="url(#b)" d="M0 0h104v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">` + service.Name + `</text><text x="28" y="14">` + service.Name + `</text><text x="78" y="15" fill="#010101" fill-opacity=".3">online</text><text x="78" y="14">online</text></g></svg>`)
|
||||
|
@ -92,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) {
|
||||
|
@ -103,7 +106,8 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
service := serv.ToService()
|
||||
r.ParseForm()
|
||||
name := r.PostForm.Get("name")
|
||||
domain := r.PostForm.Get("domain")
|
||||
|
@ -114,7 +118,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
port, _ := strconv.Atoi(r.PostForm.Get("port"))
|
||||
checkType := r.PostForm.Get("check_type")
|
||||
postData := r.PostForm.Get("post_data")
|
||||
serviceUpdate := &core.Service{
|
||||
serviceUpdate := &types.Service{
|
||||
Id: service.Id,
|
||||
Name: name,
|
||||
Domain: domain,
|
||||
|
@ -126,7 +130,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
Port: port,
|
||||
PostData: postData,
|
||||
}
|
||||
service = service.Update(serviceUpdate)
|
||||
service = core.UpdateService(serviceUpdate)
|
||||
ExecuteResponse(w, r, "service.html", service)
|
||||
}
|
||||
|
||||
|
@ -136,9 +140,9 @@ func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
|
||||
service.DeleteFailures()
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
service := serv.ToService()
|
||||
core.DeleteFailures(service)
|
||||
core.CoreApp.Services, _ = core.SelectAllServices()
|
||||
http.Redirect(w, r, "/services", http.StatusSeeOther)
|
||||
}
|
||||
|
@ -150,7 +154,8 @@ func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
vars := mux.Vars(r)
|
||||
interval := utils.StringInt(r.PostForm.Get("interval"))
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||
service := serv.ToService()
|
||||
checkin := &core.Checkin{
|
||||
Service: service.Id,
|
||||
Interval: interval,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -104,13 +104,13 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
admin := &core.User{
|
||||
admin := &types.User{
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
Email: config.Email,
|
||||
Admin: true,
|
||||
}
|
||||
admin.Create()
|
||||
core.CreateUser(admin)
|
||||
|
||||
if sample == "on" {
|
||||
core.LoadSampleData()
|
||||
|
|
|
@ -64,7 +64,7 @@ func UpdateUserHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if password != "##########" {
|
||||
user.Password = utils.HashPassword(password)
|
||||
}
|
||||
user.Update()
|
||||
core.UpdateUser(user)
|
||||
users, _ := core.SelectAllUsers()
|
||||
ExecuteResponse(w, r, "users.html", users)
|
||||
}
|
||||
|
@ -80,13 +80,13 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
|||
email := r.PostForm.Get("email")
|
||||
admin := r.PostForm.Get("admin")
|
||||
|
||||
user := &core.User{
|
||||
user := &types.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
Admin: (admin == "on"),
|
||||
}
|
||||
_, err := user.Create()
|
||||
_, err := core.CreateUser(user)
|
||||
if err != nil {
|
||||
utils.Log(2, err)
|
||||
}
|
||||
|
@ -108,6 +108,6 @@ func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, "/users", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
user.Delete()
|
||||
core.DeleteUser(user)
|
||||
http.Redirect(w, r, "/users", http.StatusSeeOther)
|
||||
}
|
||||
|
|
7
main.go
7
main.go
|
@ -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"
|
||||
|
@ -50,7 +50,6 @@ func RenderBoxes() {
|
|||
core.ScssBox = rice.MustFindBox("source/scss")
|
||||
core.JsBox = rice.MustFindBox("source/js")
|
||||
core.TmplBox = rice.MustFindBox("source/tmpl")
|
||||
core.EmailBox = rice.MustFindBox("source/emails")
|
||||
}
|
||||
|
||||
func LoadDotEnvs() {
|
||||
|
@ -124,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 {
|
||||
|
|
40
main_test.go
40
main_test.go
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/hunterlong/statup/core"
|
||||
"github.com/hunterlong/statup/handlers"
|
||||
"github.com/hunterlong/statup/notifiers"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/rendon/testcli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
|
@ -31,7 +32,7 @@ func RunInit(t *testing.T) {
|
|||
core.CoreApp = core.NewCore()
|
||||
}
|
||||
|
||||
var forceSequential chan bool = make(chan bool, 1)
|
||||
var forceSequential = make(chan bool, 1)
|
||||
|
||||
func TestRunAll(t *testing.T) {
|
||||
|
||||
|
@ -187,7 +188,6 @@ func TestAssetsCommand(t *testing.T) {
|
|||
assert.True(t, fileExists("assets/js/main.js"))
|
||||
assert.True(t, fileExists("assets/css/base.css"))
|
||||
assert.True(t, fileExists("assets/scss/base.scss"))
|
||||
assert.True(t, fileExists("assets/emails/failure.html"))
|
||||
}
|
||||
|
||||
func RunMySQLMakeConfig(t *testing.T, db string) {
|
||||
|
@ -267,22 +267,22 @@ func RunUser_SelectAll(t *testing.T) {
|
|||
}
|
||||
|
||||
func RunUser_Create(t *testing.T) {
|
||||
user := &core.User{
|
||||
user := &types.User{
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
Email: "info@testuser.com",
|
||||
Admin: true,
|
||||
}
|
||||
id, err := user.Create()
|
||||
id, err := core.CreateUser(user)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), id)
|
||||
user2 := &core.User{
|
||||
user2 := &types.User{
|
||||
Username: "superadmin",
|
||||
Password: "admin",
|
||||
Email: "info@adminer.com",
|
||||
Admin: true,
|
||||
}
|
||||
id, err = user2.Create()
|
||||
id, err = core.CreateUser(user2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(2), id)
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ func RunUser_Update(t *testing.T) {
|
|||
user, err := core.SelectUser(1)
|
||||
user.Email = "info@updatedemail.com"
|
||||
assert.Nil(t, err)
|
||||
err = user.Update()
|
||||
err = core.UpdateUser(user)
|
||||
assert.Nil(t, err)
|
||||
updatedUser, err := core.SelectUser(1)
|
||||
assert.Nil(t, err)
|
||||
|
@ -299,12 +299,12 @@ func RunUser_Update(t *testing.T) {
|
|||
}
|
||||
|
||||
func RunUser_NonUniqueCreate(t *testing.T) {
|
||||
user := &core.User{
|
||||
user := &types.User{
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
Email: "info@testuser.com",
|
||||
}
|
||||
_, err := user.Create()
|
||||
_, err := core.CreateUser(user)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
@ -312,7 +312,7 @@ func RunUser_Delete(t *testing.T) {
|
|||
user, err := core.SelectUser(2)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, user)
|
||||
err = user.Delete()
|
||||
err = core.DeleteUser(user)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
|
@ -327,11 +327,11 @@ func RunOneService_Check(t *testing.T) {
|
|||
service := core.SelectService(1)
|
||||
assert.NotNil(t, service)
|
||||
t.Log(service)
|
||||
assert.Equal(t, "Google", service.Name)
|
||||
assert.Equal(t, "Google", service.ToService().Name)
|
||||
}
|
||||
|
||||
func RunService_Create(t *testing.T) {
|
||||
service := &core.Service{
|
||||
service := &types.Service{
|
||||
Name: "test service",
|
||||
Domain: "https://google.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -340,7 +340,7 @@ func RunService_Create(t *testing.T) {
|
|||
Type: "http",
|
||||
Method: "GET",
|
||||
}
|
||||
id, err := service.Create()
|
||||
id, err := core.CreateService(service)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(5), id)
|
||||
t.Log(service)
|
||||
|
@ -371,7 +371,7 @@ func RunService_GraphData(t *testing.T) {
|
|||
}
|
||||
|
||||
func RunBadService_Create(t *testing.T) {
|
||||
service := &core.Service{
|
||||
service := &types.Service{
|
||||
Name: "Bad Service",
|
||||
Domain: "https://9839f83h72gey2g29278hd2od2d.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -380,7 +380,7 @@ func RunBadService_Create(t *testing.T) {
|
|||
Type: "http",
|
||||
Method: "GET",
|
||||
}
|
||||
id, err := service.Create()
|
||||
id, err := core.CreateService(service)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(6), id)
|
||||
}
|
||||
|
@ -388,14 +388,14 @@ func RunBadService_Create(t *testing.T) {
|
|||
func RunBadService_Check(t *testing.T) {
|
||||
service := core.SelectService(4)
|
||||
assert.NotNil(t, service)
|
||||
assert.Equal(t, "JSON API Tester", service.Name)
|
||||
assert.Equal(t, "JSON API Tester", service.ToService().Name)
|
||||
}
|
||||
|
||||
func RunDeleteService(t *testing.T) {
|
||||
service := core.SelectService(4)
|
||||
assert.NotNil(t, service)
|
||||
assert.Equal(t, "JSON API Tester", service.Name)
|
||||
err := service.Delete()
|
||||
assert.Equal(t, "JSON API Tester", service.ToService().Name)
|
||||
err := core.DeleteService(service.ToService())
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
|
@ -405,7 +405,7 @@ func RunCreateService_Hits(t *testing.T) {
|
|||
assert.NotNil(t, services)
|
||||
for i := 0; i <= 10; i++ {
|
||||
for _, s := range services {
|
||||
service := s.Check()
|
||||
service := core.ServiceCheck(s.ToService())
|
||||
assert.NotNil(t, service)
|
||||
}
|
||||
}
|
||||
|
@ -423,7 +423,7 @@ func RunService_Failures(t *testing.T) {
|
|||
t.SkipNow()
|
||||
service := core.SelectService(6)
|
||||
assert.NotNil(t, service)
|
||||
assert.NotEmpty(t, service.Failures)
|
||||
assert.NotEmpty(t, service.ToService().Failures)
|
||||
}
|
||||
|
||||
func RunService_LimitedHits(t *testing.T) {
|
||||
|
|
|
@ -20,12 +20,13 @@ const (
|
|||
var (
|
||||
emailer *Email
|
||||
emailArray []string
|
||||
emailQueue []*types.Email
|
||||
emailQueue []*EmailOutgoing
|
||||
emailBox *rice.Box
|
||||
mailer *gomail.Dialer
|
||||
)
|
||||
|
||||
type Email struct {
|
||||
*Notification
|
||||
mailer *gomail.Dialer
|
||||
}
|
||||
|
||||
// DEFINE YOUR NOTIFICATION HERE.
|
||||
|
@ -36,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",
|
||||
}},
|
||||
}}
|
||||
|
||||
|
@ -84,7 +79,9 @@ func (u *Email) Select() *Notification {
|
|||
|
||||
// WHEN NOTIFIER LOADS
|
||||
func (u *Email) Init() error {
|
||||
emailBox = rice.MustFindBox("emails")
|
||||
err := u.Install()
|
||||
utils.Log(1, fmt.Sprintf("Creating Mailer: %v:%v", u.Notification.Host, u.Notification.Port))
|
||||
|
||||
if err == nil {
|
||||
notifier, _ := SelectNotification(u.Id)
|
||||
|
@ -94,29 +91,43 @@ func (u *Email) Init() error {
|
|||
if u.Enabled {
|
||||
|
||||
utils.Log(1, fmt.Sprintf("Loading SMTP Emailer using host: %v:%v", u.Notification.Host, u.Notification.Port))
|
||||
u.mailer = gomail.NewDialer(u.Notification.Host, u.Notification.Port, u.Notification.Username, u.Notification.Password)
|
||||
u.mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
mailer = gomail.NewDialer(u.Notification.Host, u.Notification.Port, u.Notification.Username, u.Notification.Password)
|
||||
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
go u.Run()
|
||||
}
|
||||
}
|
||||
|
||||
//go u.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Email) Test() error {
|
||||
//email := &types.Email{
|
||||
// To: "info@socialeck.com",
|
||||
// Subject: "Test Email",
|
||||
// Template: "message.html",
|
||||
// Data: nil,
|
||||
// From: emailer.Var1,
|
||||
//}
|
||||
//SendEmail(core.EmailBox, email)
|
||||
if u.Enabled {
|
||||
email := &EmailOutgoing{
|
||||
To: "info@socialeck.com",
|
||||
Subject: "Test Email",
|
||||
Template: "message.html",
|
||||
Data: nil,
|
||||
From: emailer.Var1,
|
||||
}
|
||||
SendEmail(emailBox, email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -125,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 {
|
||||
|
@ -144,19 +157,30 @@ func (u *Email) Run() error {
|
|||
}
|
||||
|
||||
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
||||
func (u *Email) OnFailure() error {
|
||||
func (u *Email) OnFailure(s *types.Service) error {
|
||||
if u.Enabled {
|
||||
utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method))
|
||||
// Do failing stuff here!
|
||||
|
||||
msg := emailMessage{
|
||||
Service: s,
|
||||
}
|
||||
|
||||
email := &EmailOutgoing{
|
||||
To: "info@socialeck.com",
|
||||
Subject: fmt.Sprintf("Service %v is Failing", s.Name),
|
||||
Template: "failure.html",
|
||||
Data: msg,
|
||||
From: emailer.Var1,
|
||||
}
|
||||
SendEmail(emailBox, email)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
|
||||
func (u *Email) OnSuccess() error {
|
||||
func (u *Email) OnSuccess(s *types.Service) error {
|
||||
if u.Enabled {
|
||||
utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method))
|
||||
// Do failing stuff here!
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -172,7 +196,7 @@ func (u *Email) OnSave() error {
|
|||
|
||||
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
||||
func (u *Email) Install() error {
|
||||
inDb, err := emailer.Notification.isInDatabase()
|
||||
inDb, err := emailer.Notification.IsInDatabase()
|
||||
if !inDb {
|
||||
newNotifer, err := InsertDatabase(u.Notification)
|
||||
if err != nil {
|
||||
|
@ -184,20 +208,21 @@ 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)
|
||||
m.SetHeader("To", email.To)
|
||||
m.SetHeader("Subject", email.Subject)
|
||||
m.SetBody("text/html", email.Source)
|
||||
if err := u.mailer.DialAndSend(m); err != nil {
|
||||
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))
|
||||
return err
|
||||
}
|
||||
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)
|
||||
|
@ -221,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)
|
||||
|
|
|
@ -50,15 +50,13 @@
|
|||
|
||||
<table class="body-sub" style="border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin-top: 25px; padding-top: 25px;">
|
||||
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
|
||||
<a href="{{.Domain}}/service/{{.Service.Id}}" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">View Service</a>
|
||||
<a href="/service/{{.Service.Id}}" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">View Service</a>
|
||||
</td>
|
||||
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
|
||||
<a href="{{.Domain}}/dashboard" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">Statup Dashboard</a>
|
||||
<a href="/dashboard" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">Statup Dashboard</a>
|
||||
</td>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
|
@ -2,6 +2,7 @@ package notifiers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -9,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"`
|
||||
|
@ -43,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
|
||||
|
@ -54,21 +38,69 @@ type Notifier interface {
|
|||
Init() error
|
||||
Install() error
|
||||
Run() error
|
||||
OnFailure() error
|
||||
OnSuccess() error
|
||||
OnFailure(*types.Service) error
|
||||
OnSuccess(*types.Service) error
|
||||
Select() *Notification
|
||||
Test() error
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -81,12 +113,12 @@ func SelectNotification(id int64) (*Notification, error) {
|
|||
func (n *Notification) Update() (*Notification, error) {
|
||||
n.CreatedAt = time.Now()
|
||||
err := Collections.Find("id", n.Id).Update(n)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
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,30 +188,42 @@ 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 ""
|
||||
}
|
||||
|
||||
func OnFailure() {
|
||||
func OnFailure(s *types.Service) {
|
||||
for _, comm := range AllCommunications {
|
||||
n := comm.(Notifier)
|
||||
n.OnFailure()
|
||||
n.OnFailure(s)
|
||||
}
|
||||
}
|
||||
|
||||
func OnSuccess() {
|
||||
func OnSuccess(s *types.Service) {
|
||||
for _, comm := range AllCommunications {
|
||||
n := comm.(Notifier)
|
||||
n.OnSuccess()
|
||||
n.OnSuccess(s)
|
||||
}
|
||||
}
|
||||
|
||||
func uniqueMessages(arr []string, v string) []string {
|
||||
var newArray []string
|
||||
for _, i := range arr {
|
||||
if i != v {
|
||||
newArray = append(newArray, v)
|
||||
func uniqueStrings(elements []string) []string {
|
||||
result := []string{}
|
||||
|
||||
for i := 0; i < len(elements); i++ {
|
||||
// Scan slice for a previous element of the same value.
|
||||
exists := false
|
||||
for v := 0; v < i; v++ {
|
||||
if elements[v] == elements[i] {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If no previous element exists, append this one.
|
||||
if !exists {
|
||||
result = append(result, elements[i])
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
return result
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,25 +3,37 @@ package notifiers
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"net/http"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SLACK_ID = 2
|
||||
SLACK_METHOD = "slack"
|
||||
SLACK_ID = 2
|
||||
SLACK_METHOD = "slack"
|
||||
FAILING_TEMPLATE = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "<{{.Service.Domain}}|{{.Service.Name}}> - Your Statup service '{{.Service.Name}}' has just received a Failure notification with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected", "value": "{{.Service.Expected}}", "short": true }, { "title": "Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#FF0000", "thumb_url": "https://statup.io", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
|
||||
SUCCESS_TEMPLATE = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "<{{.Service.Domain}}|{{.Service.Name}}> - Your Statup service '{{.Service.Name}}' has just received a Failure notification.", "fields": [ { "title": "Issue", "value": "Awesome Project", "short": true }, { "title": "Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#00FF00", "thumb_url": "https://statup.io", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
|
||||
TEST_TEMPLATE = `{"text":"{{.}}"}`
|
||||
)
|
||||
|
||||
var (
|
||||
slacker *Slack
|
||||
slackMessages []string
|
||||
messageLock *sync.Mutex
|
||||
)
|
||||
|
||||
type Slack struct {
|
||||
*Notification
|
||||
}
|
||||
|
||||
type slackMessage struct {
|
||||
Service *types.Service
|
||||
Time int64
|
||||
}
|
||||
|
||||
// DEFINE YOUR NOTIFICATION HERE.
|
||||
func init() {
|
||||
slacker = &Slack{&Notification{
|
||||
|
@ -29,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.",
|
||||
|
@ -37,6 +49,7 @@ func init() {
|
|||
}}},
|
||||
}
|
||||
add(slacker)
|
||||
messageLock = new(sync.Mutex)
|
||||
}
|
||||
|
||||
// Select Obj
|
||||
|
@ -46,9 +59,7 @@ func (u *Slack) Select() *Notification {
|
|||
|
||||
// WHEN NOTIFIER LOADS
|
||||
func (u *Slack) Init() error {
|
||||
|
||||
err := u.Install()
|
||||
|
||||
if err == nil {
|
||||
notifier, _ := SelectNotification(u.Id)
|
||||
forms := u.Form
|
||||
|
@ -63,21 +74,29 @@ func (u *Slack) Init() error {
|
|||
}
|
||||
|
||||
func (u *Slack) Test() error {
|
||||
SendSlack("Slack notifications on your Statup server is working!")
|
||||
msg := fmt.Sprintf("You're Statup Slack Notifier is working correctly!")
|
||||
SendSlack(TEST_TEMPLATE, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS
|
||||
func (u *Slack) Run() error {
|
||||
messageLock.Lock()
|
||||
slackMessages = uniqueStrings(slackMessages)
|
||||
for _, msg := range slackMessages {
|
||||
utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", msg))
|
||||
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 = uniqueMessages(slackMessages, msg)
|
||||
}
|
||||
slackMessages = []string{}
|
||||
messageLock.Unlock()
|
||||
time.Sleep(60 * time.Second)
|
||||
if u.Enabled {
|
||||
u.Run()
|
||||
|
@ -86,29 +105,36 @@ func (u *Slack) Run() error {
|
|||
}
|
||||
|
||||
// CUSTOM FUNCTION FO SENDING SLACK MESSAGES
|
||||
func SendSlack(msg string) error {
|
||||
//if slackUrl == "" {
|
||||
// return errors.New("Slack Webhook URL has not been set in settings")
|
||||
//}
|
||||
fullMessage := fmt.Sprintf("{\"text\":\"%v\"}", msg)
|
||||
slackMessages = append(slackMessages, fullMessage)
|
||||
func SendSlack(temp string, data interface{}) error {
|
||||
messageLock.Lock()
|
||||
buf := new(bytes.Buffer)
|
||||
slackTemp, _ := template.New("slack").Parse(temp)
|
||||
slackTemp.Execute(buf, data)
|
||||
slackMessages = append(slackMessages, buf.String())
|
||||
messageLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
||||
func (u *Slack) OnFailure() error {
|
||||
func (u *Slack) OnFailure(s *types.Service) error {
|
||||
if u.Enabled {
|
||||
utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method))
|
||||
// Do failing stuff here!
|
||||
message := slackMessage{
|
||||
Service: s,
|
||||
Time: time.Now().Unix(),
|
||||
}
|
||||
SendSlack(FAILING_TEMPLATE, message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
|
||||
func (u *Slack) OnSuccess() error {
|
||||
func (u *Slack) OnSuccess(s *types.Service) error {
|
||||
if u.Enabled {
|
||||
utils.Log(1, fmt.Sprintf("Notification %v is receiving a successful notification.", u.Method))
|
||||
// Do checking or any successful things here
|
||||
//message := slackMessage{
|
||||
// Service: s,
|
||||
// Time: time.Now().Unix(),
|
||||
//}
|
||||
//SendSlack(SUCCESS_TEMPLATE, message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -124,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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -28,22 +28,22 @@ services:
|
|||
- internet
|
||||
- database
|
||||
depends_on:
|
||||
- postgres
|
||||
- postgres_statup
|
||||
volumes:
|
||||
- ./statup/app:/app
|
||||
environment:
|
||||
VIRTUAL_HOST: localhost
|
||||
VIRTUAL_PORT: 8080
|
||||
DB_CONN: postgres
|
||||
DB_HOST: postgres
|
||||
DB_HOST: postgres_statup
|
||||
DB_USER: statup
|
||||
DB_PASS: password123
|
||||
DB_DATABASE: statup
|
||||
NAME: EC2 Example
|
||||
DESCRIPTION: This is a Statup Docker Compose instance
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
postgres_statup:
|
||||
container_name: postgres_statup
|
||||
image: postgres
|
||||
restart: always
|
||||
networks:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{ range . }}{{ if .AvgTime }}var ctx_{{.Id}}=document.getElementById("service_{{.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{.Id}},{type:'line',data:{datasets:[{label:'Response Time (Milliseconds)',data:{{safe .GraphData}},backgroundColor:['rgba(47, 206, 30, 0.92)'],borderColor:['rgb(47, 171, 34)'],borderWidth:1}]},options:{maintainAspectRatio:!1,scaleShowValues:!0,layout:{padding:{left:0,right:0,top:0,bottom:-10}},hover:{animationDuration:0,},responsiveAnimationDuration:0,animation:{duration:3500,onComplete:function(){var chartInstance=this.chart,ctx=chartInstance.ctx;var controller=this.chart.controller;var xAxis=controller.scales['x-axis-0'];var yAxis=controller.scales['y-axis-0'];ctx.font=Chart.helpers.fontString(Chart.defaults.global.defaultFontSize,Chart.defaults.global.defaultFontStyle,Chart.defaults.global.defaultFontFamily);ctx.textAlign='center';ctx.textBaseline='bottom';var numTicks=xAxis.ticks.length;var yOffsetStart=xAxis.width/numTicks;var halfBarWidth=(xAxis.width/(numTicks*2));xAxis.ticks.forEach(function(value,index){var xOffset=20;var yOffset=(yOffsetStart*index)+halfBarWidth;ctx.fillStyle='#e2e2e2';ctx.fillText(value,yOffset,xOffset)});this.data.datasets.forEach(function(dataset,i){var meta=chartInstance.controller.getDatasetMeta(i);var hxH=0;var hyH=0;var hxL=0;var hyL=0;var highestNum=0;var lowestnum=999999999999;meta.data.forEach(function(bar,index){var data=dataset.data[index];if(lowestnum>data.y){lowestnum=data.y;hxL=bar._model.x;hyL=bar._model.y}
|
||||
{{ range . }}{{$s := .ToService}}{{ if .AvgTime }}var ctx_{{$s.Id}}=document.getElementById("service_{{$s.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{$s.Id}},{type:'line',data:{datasets:[{label:'Response Time (Milliseconds)',data:{{safe .GraphData}},backgroundColor:['rgba(47, 206, 30, 0.92)'],borderColor:['rgb(47, 171, 34)'],borderWidth:1}]},options:{maintainAspectRatio:!1,scaleShowValues:!0,layout:{padding:{left:0,right:0,top:0,bottom:-10}},hover:{animationDuration:0,},responsiveAnimationDuration:0,animation:{duration:3500,onComplete:function(){var chartInstance=this.chart,ctx=chartInstance.ctx;var controller=this.chart.controller;var xAxis=controller.scales['x-axis-0'];var yAxis=controller.scales['y-axis-0'];ctx.font=Chart.helpers.fontString(Chart.defaults.global.defaultFontSize,Chart.defaults.global.defaultFontStyle,Chart.defaults.global.defaultFontFamily);ctx.textAlign='center';ctx.textBaseline='bottom';var numTicks=xAxis.ticks.length;var yOffsetStart=xAxis.width/numTicks;var halfBarWidth=(xAxis.width/(numTicks*2));xAxis.ticks.forEach(function(value,index){var xOffset=20;var yOffset=(yOffsetStart*index)+halfBarWidth;ctx.fillStyle='#e2e2e2';ctx.fillText(value,yOffset,xOffset)});this.data.datasets.forEach(function(dataset,i){var meta=chartInstance.controller.getDatasetMeta(i);var hxH=0;var hyH=0;var hxL=0;var hyL=0;var highestNum=0;var lowestnum=999999999999;meta.data.forEach(function(bar,index){var data=dataset.data[index];if(lowestnum>data.y){lowestnum=data.y;hxL=bar._model.x;hyL=bar._model.y}
|
||||
if(data.y>highestNum){highestNum=data.y;hxH=bar._model.x;hyH=bar._model.y}});if(hxH>=820){hxH=820}else if(50>=hxH){hxH=50}
|
||||
if(hxL>=820){hxL=820}else if(70>=hxL){hxL=70}
|
||||
ctx.fillStyle='#ffa7a2';ctx.fillText(highestNum+"ms",hxH-40,hyH+15);ctx.fillStyle='#45d642';ctx.fillText(lowestnum+"ms",hxL,hyL+10);console.log("done service_id_{{.Id}}")})}},legend:{display:!1},tooltips:{"enabled":!1},scales:{yAxes:[{display:!1,ticks:{fontSize:20,display:!1,beginAtZero:!1},gridLines:{display:!1}}],xAxes:[{type:'time',distribution:'series',autoSkip:!1,gridLines:{display:!1},ticks:{stepSize:1,min:0,fontColor:"white",fontSize:20,display:!1,}}]},elements:{point:{radius:0}}}})
|
||||
ctx.fillStyle='#ffa7a2';ctx.fillText(highestNum+"ms",hxH-40,hyH+15);ctx.fillStyle='#45d642';ctx.fillText(lowestnum+"ms",hxL,hyL+10);console.log("done service_id_{{$s.Id}}")})}},legend:{display:!1},tooltips:{"enabled":!1},scales:{yAxes:[{display:!1,ticks:{fontSize:20,display:!1,beginAtZero:!1},gridLines:{display:!1}}],xAxes:[{type:'time',distribution:'series',autoSkip:!1,gridLines:{display:!1},ticks:{stepSize:1,min:0,fontColor:"white",fontSize:20,display:!1,}}]},elements:{point:{radius:0}}}})
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -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) {
|
||||
|
|
|
@ -48,36 +48,34 @@
|
|||
|
||||
<div class="list-group mb-5 mt-3">
|
||||
{{ range .Services }}
|
||||
{{ $s := .ToService }}
|
||||
<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">{{.Name}}</h5>
|
||||
<small>{{if .Online}} <span class="badge badge-success">ONLINE</span> {{else}} <span class="badge badge-danger">OFFLINE</span> {{end}}</small>
|
||||
<h5 class="mb-1">{{$s.Name}}</h5>
|
||||
<small>{{if $s.Online}} <span class="badge badge-success">ONLINE</span> {{else}} <span class="badge badge-danger">OFFLINE</span> {{end}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{.SmallText}}</p>
|
||||
</a>
|
||||
{{ 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 }}
|
||||
|
||||
|
||||
|
|
|
@ -12,25 +12,27 @@
|
|||
<link rel="stylesheet" href="/css/base.css">
|
||||
{{end}}
|
||||
|
||||
<title>{{.Core.Name}} Status</title>
|
||||
<title>{{.Name}} Status</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
|
||||
|
||||
<h1 class="col-12 text-center mb-4 mt-sm-3 header-title">{{.Core.Name}}</h1>
|
||||
<h1 class="col-12 text-center mb-4 mt-sm-3 header-title">{{.Name}}</h1>
|
||||
|
||||
{{ if .Core.Description }}
|
||||
<h5 class="col-12 text-center mb-5 header-desc">{{ .Core.Description }}</h5>
|
||||
{{ if .Description }}
|
||||
<h5 class="col-12 text-center mb-5 header-desc">{{ .Description }}</h5>
|
||||
{{ end }}
|
||||
|
||||
<div class="col-12 full-col-12 mb-5">
|
||||
<div class="list-group online_list">
|
||||
{{ range .Services }}
|
||||
<a href="#" class="service_li list-group-item list-group-item-action {{if not .Online}}bg-danger text-white{{ end }}" data-id="{{.Id}}">
|
||||
{{ .Name }}
|
||||
{{if .Online}}
|
||||
{{ $s := .ToService }}
|
||||
|
||||
<a href="#" class="service_li list-group-item list-group-item-action {{if not $s.Online}}bg-danger text-white{{ end }}" data-id="{{$s.Id}}">
|
||||
{{ $s.Name }}
|
||||
{{if $s.Online}}
|
||||
<span class="badge bg-success float-right pulse-glow">ONLINE</span>
|
||||
{{ else }}
|
||||
<span class="badge bg-white text-black-50 float-right pulse">OFFLINE</span>
|
||||
|
@ -51,12 +53,13 @@
|
|||
</div>
|
||||
{{end}}
|
||||
{{ range .Services }}
|
||||
<div class="mt-4" id="service_id_{{.Id}}">
|
||||
{{ $s := .ToService }}
|
||||
<div class="mt-4" id="service_id_{{$s.Id}}">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="col-12">
|
||||
<h4 class="mt-3"><a href="/service/{{.Id}}"{{if not .Online}} class="text-danger"{{end}}>{{ .Name }}</a>
|
||||
{{if .Online}}
|
||||
<h4 class="mt-3"><a href="/service/{{$s.Id}}"{{if not $s.Online}} class="text-danger"{{end}}>{{ $s.Name }}</a>
|
||||
{{if $s.Online}}
|
||||
<span class="badge bg-success float-right">ONLINE</span>
|
||||
{{ else }}
|
||||
<span class="badge bg-danger float-right pulse">OFFLINE</span>
|
||||
|
@ -84,13 +87,13 @@
|
|||
</div>
|
||||
{{ if .AvgTime }}
|
||||
<div class="chart-container">
|
||||
<canvas id="service_{{ .Id }}"></canvas>
|
||||
<canvas id="service_{{ $s.Id }}"></canvas>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="lower_canvas full-col-12 text-white{{if not .Online}} bg-danger{{end}}">
|
||||
<div class="lower_canvas full-col-12 text-white{{if not $s.Online}} bg-danger{{end}}">
|
||||
<div class="col-12">
|
||||
<span class="col-10 d-none d-md-inline">{{.SmallText}}</span>
|
||||
<a href="/service/{{ .Id }}" class="btn {{if .Online}}btn-success{{else}}btn-danger{{end}} btn-sm float-right col-sm-4 col-md-2 dyn-dark">View Service</a>
|
||||
<a href="/service/{{ $s.Id }}" class="btn {{if $s.Online}}btn-success{{else}}btn-danger{{end}} btn-sm float-right col-sm-4 col-md-2 dyn-dark">View Service</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -115,9 +118,9 @@
|
|||
{{end}}
|
||||
<script src="/charts.js"></script>
|
||||
|
||||
{{ if .Core.Style }}
|
||||
{{ if .Style }}
|
||||
<style>
|
||||
{{ safe .Core.Style }}
|
||||
{{ safe .Style }}
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!doctype html>
|
||||
{{ $s := .ToService }}<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -13,7 +13,7 @@
|
|||
<script src="/js/Chart.bundle.min.js"></script>
|
||||
{{end}}
|
||||
|
||||
<title>Statup | {{.Name}} Service</title>
|
||||
<title>Statup | {{$s.Name}} Service</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -25,14 +25,14 @@
|
|||
|
||||
<div class="col-12 mb-4">
|
||||
|
||||
{{if .Online }}
|
||||
{{if $s.Online }}
|
||||
<span class="mt-3 mb-3 text-white d-md-none btn bg-success d-block d-md-none">ONLINE</span>
|
||||
{{ else }}
|
||||
<span class="mt-3 mb-3 text-white d-md-none btn bg-danger d-block d-md-none">OFFLINE</span>
|
||||
{{end}}
|
||||
|
||||
<h4 class="mt-2">{{ .Name }}
|
||||
{{if .Online }}
|
||||
<h4 class="mt-2">{{ $s.Name }}
|
||||
{{if $s.Online }}
|
||||
<span class="badge bg-success float-right d-none d-md-block">ONLINE</span>
|
||||
{{ else }}
|
||||
<span class="badge bg-danger float-right d-none d-md-block">OFFLINE</span>
|
||||
|
@ -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>
|
||||
|
@ -81,65 +82,65 @@
|
|||
|
||||
<h3>Edit Service</h3>
|
||||
|
||||
<form action="/service/{{.Id}}" method="POST">
|
||||
<form action="/service/{{$s.Id}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="name" class="form-control" id="service_name" value="{{.Name}}" placeholder="Name" required>
|
||||
<input type="text" name="name" class="form-control" id="service_name" value="{{$s.Name}}" placeholder="Name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_type" class="col-sm-4 col-form-label">Service Check Type</label>
|
||||
<div class="col-sm-8">
|
||||
<select name="check_type" class="form-control" id="service_type" value="{{.Type}}">
|
||||
<option value="http" {{if eq .Type "http"}}selected{{end}}>HTTP Service</option>
|
||||
<option value="tcp" {{if eq .Type "tcp"}}selected{{end}}>TCP Service</option>
|
||||
<select name="check_type" class="form-control" id="service_type" value="{{$s.Type}}">
|
||||
<option value="http" {{if eq $s.Type "http"}}selected{{end}}>HTTP Service</option>
|
||||
<option value="tcp" {{if eq $s.Type "tcp"}}selected{{end}}>TCP Service</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com" required>
|
||||
<input type="text" name="domain" class="form-control" id="service_url" value="{{$s.Domain}}" placeholder="https://google.com" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label>
|
||||
<div class="col-sm-8">
|
||||
<select name="method" class="form-control" id="service_check_type" value="{{.Method}}">
|
||||
<option value="GET" {{if eq .Method "GET"}}selected{{end}}>GET</option>
|
||||
<option value="POST" {{if eq .Method "POST"}}selected{{end}}>POST</option>
|
||||
<select name="method" class="form-control" id="service_check_type" value="{{$s.Method}}">
|
||||
<option value="GET" {{if eq $s.Method "GET"}}selected{{end}}>GET</option>
|
||||
<option value="POST" {{if eq $s.Method "POST"}}selected{{end}}>POST</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="post_data" class="col-sm-4 col-form-label">Post Data (JSON)</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea name="post_data" class="form-control" id="post_data" rows="3">{{.PostData}}</textarea>
|
||||
<textarea name="post_data" class="form-control" id="post_data" rows="3">{{$s.PostData}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<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">{{.Expected}}</textarea>
|
||||
<textarea name="expected" class="form-control" id="service_response" rows="3">{{$s.Expected}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="service_response_code">
|
||||
<input type="number" name="expected_status" class="form-control" value="{{$s.ExpectedStatus}}" id="service_response_code">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_port" class="col-sm-4 col-form-label">TCP Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="port" class="form-control" value="{{.Port}}" id="service_port" placeholder="8080">
|
||||
<input type="number" name="port" class="form-control" value="{{$s.Port}}" id="service_port" placeholder="8080">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="interval" class="form-control" value="{{.Interval}}" id="service_interval" required>
|
||||
<input type="number" name="interval" class="form-control" value="{{$s.Interval}}" id="service_interval" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -147,7 +148,7 @@
|
|||
<button type="submit" class="btn btn-success btn-block">Update Service</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="/service/{{ .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,13 +157,11 @@
|
|||
|
||||
<div class="col-12 mt-4">
|
||||
<h3>Last Response</h3>
|
||||
|
||||
<textarea rows="8" class="form-control" readonly>{{ .LastResponse }}</textarea>
|
||||
|
||||
<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">
|
||||
<input type="text" id="last_status_code" class="form-control" value="{{ .LastStatusCode }}" readonly>
|
||||
<input type="text" id="last_status_code" class="form-control" value="{{ $s.LastStatusCode }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -171,12 +170,12 @@
|
|||
|
||||
<div class="col-12 mt-4">
|
||||
<h3>Service Checkins</h3>
|
||||
{{ range .Checkins }}
|
||||
{{ range $s.Checkins }}
|
||||
<h5>Check #{{.Id}} <span class="badge online_badge float-right">Checked in {{.Ago}}</span></h5>
|
||||
<input type="text" class="form-control" value="https://domainhere.com/api/checkin/{{.Api}}">
|
||||
{{ end }}
|
||||
|
||||
<form action="/service/{{.Id}}/checkin" method="POST">
|
||||
<form action="/service/{{$s.Id}}/checkin" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="service_name" class="col-sm-4 col-form-label">Check Interval (in seconds)</label>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
|
|
|
@ -33,13 +33,14 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
{{ $s := .ToService }}
|
||||
<tr>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
|
||||
<td>{{$s.Name}}</td>
|
||||
<td>{{if $s.Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/service/{{.Id}}" class="btn btn-primary">View</a>
|
||||
<a href="/service/{{.Id}}/delete" class="btn btn-danger">Delete</a>
|
||||
<a href="/service/{{$s.Id}}" class="btn btn-primary">View</a>
|
||||
<a href="/service/{{$s.Id}}/delete" class="btn btn-danger confirm-btn">Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -47,7 +48,6 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<h3>Create Service</h3>
|
||||
|
||||
<form action="/services" method="POST">
|
||||
|
@ -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">
|
||||
|
|
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package types
|
||||
|
||||
type ServiceInterface interface {
|
||||
AvgTime() float64
|
||||
Online24() float32
|
||||
ToService() *Service
|
||||
SmallText() string
|
||||
GraphData() string
|
||||
AvgUptime() string
|
||||
LimitedFailures() []*Failure
|
||||
TotalFailures() (uint64, error)
|
||||
TotalFailures24Hours() (uint64, error)
|
||||
}
|
||||
|
||||
type FailureInterface interface {
|
||||
ToFailure() *Failure
|
||||
Ago() string
|
||||
ParseError() string
|
||||
}
|
100
types/types.go
100
types/types.go
|
@ -1,9 +1,99 @@
|
|||
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"`
|
||||
Domain string `db:"domain" json:"domain"`
|
||||
Expected string `db:"expected" json:"expected"`
|
||||
ExpectedStatus int `db:"expected_status" json:"expected_status"`
|
||||
Interval int `db:"check_interval" json:"check_interval"`
|
||||
Type string `db:"check_type" json:"type"`
|
||||
Method string `db:"method" json:"method"`
|
||||
PostData string `db:"post_data" json:"post_data"`
|
||||
Port int `db:"port" json:"port"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Online bool `json:"online"`
|
||||
Latency float64 `json:"latency"`
|
||||
Online24Hours float32 `json:"24_hours_online"`
|
||||
AvgResponse string `json:"avg_response"`
|
||||
TotalUptime string `json:"uptime"`
|
||||
OrderId int64 `json:"order_id"`
|
||||
Failures []*Failure `json:"failures"`
|
||||
Checkins []*Checkin `json:"checkins"`
|
||||
StopRoutine chan struct{}
|
||||
LastResponse string
|
||||
LastStatusCode int
|
||||
LastOnline time.Time
|
||||
DnsLookup float64 `json:"dns_lookup_time"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id int64 `db:"id,omitempty" json:"id"`
|
||||
Username string `db:"username" json:"username"`
|
||||
|
@ -40,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"`
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue