pull/429/head
hunterlong 2020-02-18 20:07:22 -08:00
parent f82abe9b49
commit d2331fe14b
56 changed files with 671 additions and 9469 deletions

View File

@ -65,7 +65,7 @@ func catchCLI(args []string) error {
if err := runAssets(); err != nil {
return err
}
if err := source.CompileSASS(dir); err != nil {
if err := source.CompileSASS(); err != nil {
return err
}
return errors.New("end")

View File

@ -93,7 +93,7 @@ func (c *Checkin) CreateFailure() (int64, error) {
Checkin: c.Id,
PingTime: c.Expected().Seconds(),
}}
row := failuresDB().Create(&fail)
row := Database(&Failure{}).Create(&fail)
sort.Sort(types.FailSort(c.Failures))
c.Failures = append(c.Failures, fail)
if len(c.Failures) > limitedFailures {
@ -105,14 +105,14 @@ func (c *Checkin) CreateFailure() (int64, error) {
// LimitedHits will return the last amount of successful hits from a checkin
func (c *Checkin) LimitedHits(amount int) []*types.CheckinHit {
var hits []*types.CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
Database(&CheckinHit{}).Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
return hits
}
// AllCheckins returns all checkin in system
func AllCheckins() []*Checkin {
var checkins []*Checkin
checkinDB().Find(&checkins)
Database(&types.Checkin{}).Find(&checkins)
return checkins
}
@ -152,7 +152,7 @@ func (c *Checkin) Expected() time.Duration {
// Last returns the last checkinHit for a Checkin
func (c *Checkin) Last() *CheckinHit {
var hit CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit)
Database(c).Where("checkin = ?", c.Id).Last(&hit)
return &hit
}
@ -163,7 +163,7 @@ func (c *Checkin) Link() string {
// AllHits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllHits() []*types.CheckinHit {
var checkins []*types.CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins)
Database(&types.CheckinHit{}).Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins)
return checkins
}
@ -171,7 +171,7 @@ func (c *Checkin) AllHits() []*types.CheckinHit {
func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface {
var failures []*Failure
var failInterfaces []types.FailureInterface
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
col := Database(&types.Failure{}).Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
col.Find(&failures)
for _, f := range failures {
failInterfaces = append(failInterfaces, f)
@ -182,7 +182,7 @@ func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface {
// Hits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllFailures() []*types.Failure {
var failures []*types.Failure
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Order("id desc")
col := Database(&types.Failure{}).Where("checkin = ?", c.Id).Where("method = 'checkin'").Order("id desc")
col.Find(&failures)
return failures
}
@ -194,7 +194,7 @@ func (c *Checkin) Delete() error {
service := c.Service()
slice := service.Checkins
service.Checkins = append(slice[:i], slice[i+1:]...)
row := checkinDB().Delete(&c)
row := Database(c).Delete(&c)
return row.Error()
}
@ -211,7 +211,7 @@ func (c *Checkin) index() int {
// Create will create a new Checkin
func (c *Checkin) Create() (int64, error) {
c.ApiKey = utils.RandomString(7)
row := checkinDB().Create(&c)
row := Database(c).Create(&c)
if row.Error() != nil {
log.Warnln(row.Error())
return 0, row.Error()
@ -225,7 +225,7 @@ func (c *Checkin) Create() (int64, error) {
// Update will update a Checkin
func (c *Checkin) Update() (int64, error) {
row := checkinDB().Update(&c)
row := Database(c).Update(&c)
if row.Error() != nil {
log.Warnln(row.Error())
return 0, row.Error()
@ -238,7 +238,7 @@ func (c *CheckinHit) Create() (int64, error) {
if c.CreatedAt.IsZero() {
c.CreatedAt = utils.Now()
}
row := checkinHitsDB().Create(&c)
row := Database(c).Create(&c)
if row.Error() != nil {
log.Warnln(row.Error())
return 0, row.Error()

View File

@ -83,7 +83,7 @@ func InsertNotifierDB() error {
return errors.New("database connection has not been created")
}
}
notifier.SetDB(DbSession, CoreApp.Timezone)
notifier.SetDB(DbSession)
return nil
}
@ -101,7 +101,7 @@ func InsertIntegratorDB() error {
// UpdateCore will update the CoreApp variable inside of the 'core' table in database
func UpdateCore(c *Core) (*Core, error) {
db := coreDB().Update(&c)
db := Database(&Core{}).Update(&c)
return c, db.Error()
}
@ -116,7 +116,7 @@ func (c Core) CurrentTime() string {
// Messages will return the current local time
func (c Core) Messages() []*Message {
var message []*Message
messagesDb().Where("service = ?", 0).Limit(10).Find(&message)
Database(&Message{}).Where("service = ?", 0).Limit(10).Find(&message)
return message
}
@ -130,7 +130,7 @@ func (c Core) SassVars() string {
if !source.UsingAssets(utils.Directory) {
return ""
}
return source.OpenAsset(utils.Directory, "scss/variables.scss")
return source.OpenAsset("scss/variables.scss")
}
// BaseSASS is the base design , this opens the file /assets/scss/base.scss to be edited in Theme
@ -138,7 +138,7 @@ func (c Core) BaseSASS() string {
if !source.UsingAssets(utils.Directory) {
return ""
}
return source.OpenAsset(utils.Directory, "scss/base.scss")
return source.OpenAsset("scss/base.scss")
}
// MobileSASS is the -webkit responsive custom css designs. This opens the
@ -147,7 +147,7 @@ func (c Core) MobileSASS() string {
if !source.UsingAssets(utils.Directory) {
return ""
}
return source.OpenAsset(utils.Directory, "scss/mobile.scss")
return source.OpenAsset("scss/mobile.scss")
}
// AllOnline will be true if all services are online
@ -171,7 +171,7 @@ func SelectCore() (*Core, error) {
log.Errorf("core database has not been setup yet, does not have the 'core' table")
return nil, errors.New("core database has not been setup yet.")
}
db := coreDB().First(&CoreApp)
db := Database(&Core{}).First(&CoreApp)
if db.Error() != nil {
return nil, db.Error()
}

View File

@ -47,68 +47,42 @@ func init() {
// DbConfig stores the config.yml file for the statup configuration
type DbConfig types.DbConfig
// failuresDB returns the 'failures' database column
func failuresDB() types.Database {
return DbSession.Model(&types.Failure{})
}
// hitsDB returns the 'hits' database column
func hitsDB() types.Database {
return DbSession.Model(&types.Hit{})
}
// servicesDB returns the 'services' database column
func servicesDB() types.Database {
return DbSession.Model(&types.Service{})
}
// coreDB returns the single column 'core'
func coreDB() types.Database {
return DbSession.Table("core").Model(&CoreApp)
}
// usersDB returns the 'users' database column
func usersDB() types.Database {
return DbSession.Model(&types.User{})
}
// checkinDB returns the Checkin records for a service
func checkinDB() types.Database {
return DbSession.Model(&types.Checkin{})
}
// checkinHitsDB returns the Checkin Hits records for a service
func checkinHitsDB() types.Database {
return DbSession.Model(&types.CheckinHit{})
}
// messagesDb returns the Checkin records for a service
func messagesDb() types.Database {
return DbSession.Model(&types.Message{})
}
// messagesDb returns the Checkin records for a service
func groupsDb() types.Database {
return DbSession.Model(&types.Group{})
}
// incidentsDB returns the 'incidents' database column
func incidentsDB() types.Database {
return DbSession.Model(&types.Incident{})
}
// incidentsUpdatesDB returns the 'incidents updates' database column
func incidentsUpdatesDB() types.Database {
return DbSession.Model(&types.IncidentUpdate{})
func Database(obj interface{}) types.Database {
switch obj.(type) {
case *types.Service, *Service, []*Service:
return DbSession.Model(&types.Service{})
case *types.Hit, *Hit, []*Hit:
return DbSession.Model(&types.Hit{})
case *types.Failure, *Failure, []*Failure:
return DbSession.Model(&types.Failure{})
case *types.Core, *Core:
return DbSession.Table("core").Model(&CoreApp)
case *types.Checkin, *Checkin, []*Checkin:
return DbSession.Model(&types.Checkin{})
case *types.CheckinHit, *CheckinHit, []*CheckinHit:
return DbSession.Model(&types.CheckinHit{})
case *types.User, *User, []*User:
return DbSession.Model(&types.User{})
case *types.Group, *Group, []*Group:
return DbSession.Model(&types.Group{})
case *types.Incident, *Incident, []*Incident:
return DbSession.Model(&types.Incident{})
case *types.IncidentUpdate, *IncidentUpdate, []*IncidentUpdate:
return DbSession.Model(&types.IncidentUpdate{})
case *types.Message, *Message, []*Message:
return DbSession.Model(&types.Message{})
default:
return DbSession
}
}
// HitsBetween returns the gorm database query for a collection of service hits between a time range
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) types.Database {
selector := Dbtimestamp(group, column)
if CoreApp.Config.DbConn == "postgres" {
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
} else {
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
}
}
@ -186,7 +160,7 @@ func (c *Core) InsertCore(db *types.DbConfig) (*Core, error) {
MigrationId: time.Now().Unix(),
Config: db,
}}
query := coreDB().Create(&CoreApp)
query := Database(CoreApp).Create(&CoreApp)
return CoreApp, query.Error()
}
@ -345,7 +319,7 @@ func (c *Core) CreateCore() *Core {
Domain: c.Domain,
MigrationId: time.Now().Unix(),
}
db := coreDB().Create(&newCore)
db := Database(newCore).Create(&newCore)
if db.Error() == nil {
CoreApp = &Core{Core: newCore}
}
@ -409,7 +383,7 @@ func (c *Core) MigrateDatabase() error {
}
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error() != nil {
tx.Rollback()
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error()))
return tx.Error()
}
log.Infoln("Statping Database Migrated")

View File

@ -37,7 +37,7 @@ const (
// CreateFailure will create a new Failure record for a service
func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
f.Service = s.Id
row := failuresDB().Create(f)
row := Database(&types.Failure{}).Create(f)
if row.Error() != nil {
log.Errorln(row.Error())
return 0, row.Error()
@ -62,7 +62,7 @@ func (s *Service) AllFailures() []types.Failure {
}
func (s *Service) FailuresDb(r *http.Request) types.Database {
return failuresDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
return Database(&types.Failure{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
}
// DeleteFailures will delete all failures for a service
@ -77,14 +77,14 @@ func (s *Service) DeleteFailures() {
// LimitedFailures will return the last amount of failures from a service
func (s *Service) LimitedFailures(amount int) []*Failure {
var failArr []*Failure
failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
Database(&types.Failure{}).Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
return failArr
}
// LimitedFailures will return the last amount of failures from a service
func (s *Service) LimitedCheckinFailures(amount int) []*Failure {
var failArr []*Failure
failuresDB().Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
Database(&types.Failure{}).Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
return failArr
}
@ -101,7 +101,7 @@ func (f *Failure) Select() *types.Failure {
// Delete will remove a Failure record from the database
func (f *Failure) Delete() error {
db := failuresDB().Delete(f)
db := Database(&types.Failure{}).Delete(f)
return db.Error()
}
@ -119,9 +119,9 @@ func (c *Core) Count24HFailures() uint64 {
// CountFailures returns the total count of failures for all services
func CountFailures() uint64 {
var count uint64
err := failuresDB().Count(&count)
err := Database(&types.Failure{}).Count(&count)
if err.Error() != nil {
log.Warnln(err.Error)
log.Warnln(err.Error())
return 0
}
return count
@ -132,7 +132,7 @@ func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
var count uint64
date := ago.UTC().Format("2006-01-02 00:00:00")
dateend := ago.UTC().Format("2006-01-02") + " 23:59:59"
rows := failuresDB().Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
rows := Database(&types.Failure{}).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
err := rows.Count(&count)
return count, err.Error()
}
@ -146,7 +146,7 @@ func (s *Service) TotalFailures24() (uint64, error) {
// TotalFailures returns the total amount of failures for a service
func (s *Service) TotalFailures() (uint64, error) {
var count uint64
rows := failuresDB().Where("service = ?", s.Id)
rows := Database(&types.Failure{}).Where("service = ?", s.Id)
err := rows.Count(&count)
return count, err.Error()
}
@ -161,7 +161,7 @@ func (s *Service) FailuresDaysAgo(days int) uint64 {
// TotalFailuresSince returns the total amount of failures for a service since a specific time/date
func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
var count uint64
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'")
rows := Database(&types.Failure{}).Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'")
err := rows.Count(&count)
return count, err.Error()
}

View File

@ -16,21 +16,21 @@ func (g *Group) Delete() error {
s.GroupId = 0
s.Update(false)
}
err := groupsDb().Delete(g)
err := Database(&Group{}).Delete(g)
return err.Error()
}
// Create will create a group and insert it into the database
func (g *Group) Create() (int64, error) {
g.CreatedAt = time.Now().UTC()
db := groupsDb().Create(g)
db := Database(&Group{}).Create(g)
return g.Id, db.Error()
}
// Update will update a group
func (g *Group) Update() (int64, error) {
g.UpdatedAt = time.Now().UTC()
db := groupsDb().Update(g)
db := Database(&Group{}).Update(g)
return g.Id, db.Error()
}
@ -64,7 +64,7 @@ func (g *Group) VisibleServices(auth bool) []*Service {
func SelectGroups(includeAll bool, auth bool) []*Group {
var groups []*Group
var validGroups []*Group
groupsDb().Find(&groups).Order("order_id desc")
Database(&Group{}).Find(&groups).Order("order_id desc")
for _, g := range groups {
if !g.Public.Bool {
if auth {

View File

@ -27,7 +27,7 @@ type Hit struct {
// CreateHit will create a new 'hit' record in the database for a successful/online service
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
db := hitsDB().Create(&h)
db := Database(&types.Hit{}).Create(&h)
if db.Error() != nil {
log.Errorln(db.Error())
return 0, db.Error()
@ -38,7 +38,7 @@ func (s *Service) CreateHit(h *types.Hit) (int64, error) {
// CountHits returns a int64 for all hits for a service
func (s *Service) CountHits() (int64, error) {
var hits int64
col := hitsDB().Where("service = ?", s.Id)
col := Database(&types.Hit{}).Where("service = ?", s.Id)
err := col.Count(&hits)
return hits, err.Error()
}
@ -46,19 +46,19 @@ func (s *Service) CountHits() (int64, error) {
// Hits returns all successful hits for a service
func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) {
var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
col := Database(&types.Hit{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
err := col.Find(&hits)
return hits, err.Error()
}
func (s *Service) HitsDb(r *http.Request) types.Database {
return hitsDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
return Database(&types.Hit{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
}
// Hits returns all successful hits for a service
func (s *Service) Hits() ([]*types.Hit, error) {
var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).Order("id desc")
col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc")
err := col.Find(&hits)
return hits, err.Error()
}
@ -66,7 +66,7 @@ func (s *Service) Hits() ([]*types.Hit, error) {
// LimitedHits returns the last 1024 successful/online 'hit' records for a service
func (s *Service) LimitedHits(amount int) ([]*types.Hit, error) {
var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).Order("id desc").Limit(amount)
col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc").Limit(amount)
err := col.Find(&hits)
return reverseHits(hits), err.Error()
}
@ -82,21 +82,21 @@ func reverseHits(input []*types.Hit) []*types.Hit {
// TotalHits returns the total amount of successful hits a service has
func (s *Service) TotalHits() (uint64, error) {
var count uint64
col := hitsDB().Where("service = ?", s.Id).Count(&count)
col := Database(&types.Hit{}).Where("service = ?", s.Id).Count(&count)
return count, col.Error()
}
// TotalHitsSince returns the total amount of hits based on a specific time/date
func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
var count uint64
rows := hitsDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Count(&count)
rows := Database(&types.Hit{}).Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Count(&count)
return count, rows.Error()
}
// Sum returns the added value Latency for all of the services successful hits.
func (s *Service) Sum() float64 {
var sum float64
rows, _ := hitsDB().Where("service = ?", s.Id).Select("sum(latency) as total").Rows()
rows, _ := Database(&types.Hit{}).Where("service = ?", s.Id).Select("sum(latency) as total").Rows()
for rows.Next() {
rows.Scan(&sum)
}

View File

@ -21,17 +21,17 @@ func ReturnIncident(u *types.Incident) *Incident {
// SelectIncident returns the Incident based on the Incident's ID.
func SelectIncident(id int64) (*Incident, error) {
var incident Incident
err := incidentsDB().Where("id = ?", id).First(&incident)
err := Database(incident).Where("id = ?", id).First(&incident)
return &incident, err.Error()
}
// AllIncidents will return all incidents and updates recorded
func AllIncidents() []*Incident {
var incidents []*Incident
incidentsDB().Find(&incidents).Order("id desc")
Database(incidents).Find(&incidents).Order("id desc")
for _, i := range incidents {
var updates []*types.IncidentUpdate
incidentsUpdatesDB().Find(&updates).Order("id desc")
Database(updates).Find(&updates).Order("id desc")
i.Updates = updates
}
return incidents
@ -40,46 +40,46 @@ func AllIncidents() []*Incident {
// Incidents will return the all incidents for a service
func (s *Service) Incidents() []*Incident {
var incidentArr []*Incident
incidentsDB().Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
Database(incidentArr).Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
return incidentArr
}
// AllUpdates will return the all updates for an incident
func (i *Incident) AllUpdates() []*IncidentUpdate {
var updatesArr []*IncidentUpdate
incidentsUpdatesDB().Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
Database(updatesArr).Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
return updatesArr
}
// Delete will remove a incident
func (i *Incident) Delete() error {
err := incidentsDB().Delete(i)
err := Database(i).Delete(i)
return err.Error()
}
// Create will create a incident and insert it into the database
func (i *Incident) Create() (int64, error) {
i.CreatedAt = time.Now().UTC()
db := incidentsDB().Create(i)
db := Database(i).Create(i)
return i.Id, db.Error()
}
// Update will update a incident
func (i *Incident) Update() (int64, error) {
i.UpdatedAt = time.Now().UTC()
db := incidentsDB().Update(i)
db := Database(i).Update(i)
return i.Id, db.Error()
}
// Delete will remove a incident update
func (i *IncidentUpdate) Delete() error {
err := incidentsUpdatesDB().Delete(i)
err := Database(i).Delete(i)
return err.Error()
}
// Create will create a incident update and insert it into the database
func (i *IncidentUpdate) Create() (int64, error) {
i.CreatedAt = time.Now().UTC()
db := incidentsUpdatesDB().Create(i)
db := Database(i).Create(i)
return i.Id, db.Error()
}

View File

@ -8,7 +8,7 @@ import (
)
func TestCsvFileIntegration(t *testing.T) {
data, err := ioutil.ReadFile("../../source/tmpl/bulk_import.csv")
data, err := ioutil.ReadFile("testdata/bulk_import.csv")
require.Nil(t, err)
t.Run("Set Field Value", func(t *testing.T) {

View File

@ -8,6 +8,8 @@ import (
func TestDockerIntegration(t *testing.T) {
t.SkipNow()
t.Run("Set Field Value", func(t *testing.T) {
formPost := map[string][]string{}
formPost["path"] = []string{"unix:///var/run/docker.sock"}

View File

@ -1,15 +0,0 @@
package integrations
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestIntegrations(t *testing.T) {
t.Run("Collect Integrations", func(t *testing.T) {
amount := len(Integrations)
assert.Equal(t, 3, amount)
})
}

View File

@ -0,0 +1,11 @@
name,domain,expected,expected_status,interval,type,method,post_data,port,timeout,order,allow_notifications,public,group_id,headers,permalink,verify_ssl
Bulk Upload,http://google.com,,200,60s,http,get,,,60s,1,TRUE,TRUE,,Authorization=example,bulk_example,FALSE
JSON Post,https://jsonplaceholder.typicode.com/posts,,200,1m,http,post,"{""id"": 1, ""title"": 'foo', ""body"": 'bar', ""userId"": 1}",,15s,2,TRUE,TRUE,,Content-Type=application/json,json_post_example,FALSE
Google DNS,8.8.8.8,,,60s,tcp,,,53,10s,3,TRUE,TRUE,,,google_dns_example,FALSE
Google DNS UDP,8.8.8.8,,,60s,udp,,,53,10s,4,TRUE,TRUE,,,google_dns_udp_example,FALSE
Statping Demo Page,https://demo.statping.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,5,TRUE,TRUE,,,demo_link,FALSE
Statping MySQL Page,https://mysql.statping.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,6,TRUE,TRUE,,,mysql_demo_link,FALSE
Statping SQLite Page,https://sqlite.statping.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,7,TRUE,TRUE,,,sqlite_demo_link,FALSE
Token Balance,https://status.tokenbalance.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,8,TRUE,TRUE,,,token_balance,FALSE
CloudFlare DNS,1.1.1.1,,,60s,tcp,,,53,10s,9,TRUE,TRUE,,,cloudflare_dns_example,FALSE
Verisign DNS,64.6.64.4,,,60s,tcp,,,53,10s,10,TRUE,TRUE,,,verisign_dns_example,FALSE
1 name domain expected expected_status interval type method post_data port timeout order allow_notifications public group_id headers permalink verify_ssl
2 Bulk Upload http://google.com 200 60s http get 60s 1 TRUE TRUE Authorization=example bulk_example FALSE
3 JSON Post https://jsonplaceholder.typicode.com/posts 200 1m http post {"id": 1, "title": 'foo', "body": 'bar', "userId": 1} 15s 2 TRUE TRUE Content-Type=application/json json_post_example FALSE
4 Google DNS 8.8.8.8 60s tcp 53 10s 3 TRUE TRUE google_dns_example FALSE
5 Google DNS UDP 8.8.8.8 60s udp 53 10s 4 TRUE TRUE google_dns_udp_example FALSE
6 Statping Demo Page https://demo.statping.com/health (\"online\": true) 200 30s http get 10s 5 TRUE TRUE demo_link FALSE
7 Statping MySQL Page https://mysql.statping.com/health (\"online\": true) 200 30s http get 10s 6 TRUE TRUE mysql_demo_link FALSE
8 Statping SQLite Page https://sqlite.statping.com/health (\"online\": true) 200 30s http get 10s 7 TRUE TRUE sqlite_demo_link FALSE
9 Token Balance https://status.tokenbalance.com/health (\"online\": true) 200 30s http get 10s 8 TRUE TRUE token_balance FALSE
10 CloudFlare DNS 1.1.1.1 60s tcp 53 10s 9 TRUE TRUE cloudflare_dns_example FALSE
11 Verisign DNS 64.6.64.4 60s tcp 53 10s 10 TRUE TRUE verisign_dns_example FALSE

View File

@ -8,15 +8,15 @@ import (
func TestTraefikIntegration(t *testing.T) {
t.SkipNow()
t.Run("List Services from Traefik", func(t *testing.T) {
t.SkipNow()
services, err := TraefikIntegrator.List()
require.Nil(t, err)
assert.NotEqual(t, 0, len(services))
})
t.Run("Confirm Services from Traefik", func(t *testing.T) {
t.SkipNow()
services, err := TraefikIntegrator.List()
require.Nil(t, err)
for _, s := range services {

View File

@ -28,7 +28,7 @@ type Message struct {
// SelectServiceMessages returns all messages for a service
func SelectServiceMessages(id int64) []*Message {
var message []*Message
messagesDb().Where("service = ?", id).Limit(10).Find(&message)
Database(&Message{}).Where("service = ?", id).Limit(10).Find(&message)
return message
}
@ -40,14 +40,14 @@ func ReturnMessage(m *types.Message) *Message {
// SelectMessages returns all messages
func SelectMessages() ([]*Message, error) {
var messages []*Message
db := messagesDb().Find(&messages).Order("id desc")
db := Database(&Message{}).Find(&messages).Order("id desc")
return messages, db.Error()
}
// SelectMessage returns a Message based on the ID passed
func SelectMessage(id int64) (*Message, error) {
var message Message
db := messagesDb().Where("id = ?", id).Find(&message)
db := Database(&Message{}).Where("id = ?", id).Find(&message)
return &message, db.Error()
}
@ -61,7 +61,7 @@ func (m *Message) Service() *Service {
// Create will create a Message and insert it into the database
func (m *Message) Create() (int64, error) {
m.CreatedAt = time.Now().UTC()
db := messagesDb().Create(m)
db := Database(&Message{}).Create(m)
if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error()))
return 0, db.Error()
@ -71,13 +71,13 @@ func (m *Message) Create() (int64, error) {
// Delete will delete a Message from database
func (m *Message) Delete() error {
db := messagesDb().Delete(m)
db := Database(&Message{}).Delete(m)
return db.Error()
}
// Update will update a Message in the database
func (m *Message) Update() (*Message, error) {
db := messagesDb().Update(m)
db := Database(&Message{}).Update(m)
if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error()))
return nil, db.Error()

View File

@ -93,8 +93,8 @@ type NotificationLog struct {
// AfterFind for Notification will set the timezone
func (n *Notification) AfterFind() (err error) {
n.CreatedAt = utils.Timezoner(n.CreatedAt, timezone)
n.UpdatedAt = utils.Timezoner(n.UpdatedAt, timezone)
n.CreatedAt = utils.Now()
n.UpdatedAt = utils.Now()
return
}
@ -116,9 +116,8 @@ func modelDb(n *Notification) types.Database {
}
// SetDB is called by core to inject the database for a notifier to use
func SetDB(d types.Database, zone float32) {
func SetDB(d types.Database) {
db = d
timezone = zone
}
// asNotification accepts a Notifier and returns a Notification struct
@ -247,8 +246,8 @@ func Init(n Notifier) (*Notification, error) {
var notify *Notification
if err == nil {
notify, _ = SelectNotification(n)
notify.CreatedAt = utils.Timezoner(notify.CreatedAt, timezone)
notify.UpdatedAt = utils.Timezoner(notify.UpdatedAt, timezone)
notify.CreatedAt = time.Now().UTC()
notify.UpdatedAt = time.Now().UTC()
if notify.Delay.Seconds() == 0 {
notify.Delay = time.Duration(1 * time.Second)
}
@ -350,7 +349,7 @@ func (n *Notification) SentLastMinute() int {
func (n *Notification) SentLast(since time.Time) int {
sent := 0
for _, v := range n.Logs() {
lastTime := time.Time(v.Time)
lastTime := time.Time(v.Time).UTC()
if lastTime.After(since) {
sent++
}

View File

@ -19,7 +19,6 @@ import (
"fmt"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/stretchr/testify/assert"
"testing"
@ -56,19 +55,20 @@ var core = &types.Core{
}
func injectDatabase() {
utils.DeleteFile(dir + "/notifier.db")
db, _ = gorm.Open("sqlite3", dir+"/notifier.db")
sqlPath := dir + "/notifier.db"
utils.DeleteFile(sqlPath)
db, _ = types.Openw("sqlite3", sqlPath)
db.CreateTable(&Notification{})
}
func TestIsBasicType(t *testing.T) {
assert.True(t, isType(example, new(Notifier)))
assert.True(t, isType(example, new(BasicEvents)))
assert.True(t, isType(example, new(ServiceEvents)))
assert.True(t, isType(example, new(UserEvents)))
assert.True(t, isType(example, new(CoreEvents)))
assert.True(t, isType(example, new(NotifierEvents)))
assert.True(t, isType(example, new(Tester)))
assert.True(t, utils.IsType(example, new(Notifier)))
assert.True(t, utils.IsType(example, new(BasicEvents)))
assert.True(t, utils.IsType(example, new(ServiceEvents)))
assert.True(t, utils.IsType(example, new(UserEvents)))
assert.True(t, utils.IsType(example, new(CoreEvents)))
assert.True(t, utils.IsType(example, new(NotifierEvents)))
assert.True(t, utils.IsType(example, new(Tester)))
}
func TestIsInDatabase(t *testing.T) {

View File

@ -207,7 +207,7 @@ func insertSampleCheckins() error {
// InsertSampleHits will create a couple new hits for the sample services
func InsertSampleHits() error {
tx := hitsDB().Begin()
tx := Database(&Hit{}).Begin()
sg := new(sync.WaitGroup)
for i := int64(1); i <= 5; i++ {
sg.Add(1)
@ -250,7 +250,7 @@ func insertSampleCore() error {
CreatedAt: time.Now().UTC(),
UseCdn: types.NewNullBool(false),
}
query := coreDB().Create(core)
query := Database(&Core{}).Create(core)
return query.Error()
}
@ -510,6 +510,7 @@ func TmpRecords(dbFile string) error {
var err error
CoreApp = NewCore()
CoreApp.Name = "Tester"
CoreApp.Setup = true
configs := &types.DbConfig{
DbConn: "sqlite",
Project: "Tester",

View File

@ -103,14 +103,14 @@ func (s *Service) CheckinProcess() {
// AllCheckins will return a slice of AllCheckins for a Service
func (s *Service) AllCheckins() []*Checkin {
var checkin []*Checkin
checkinDB().Where("service = ?", s.Id).Find(&checkin)
Database(&Checkin{}).Where("service = ?", s.Id).Find(&checkin)
return checkin
}
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
var services []*Service
db := servicesDB().Find(&services).Order("order_id desc")
db := Database(&Service{}).Find(&services).Order("order_id desc")
if db.Error() != nil {
log.Errorln(fmt.Sprintf("service error: %v", db.Error()))
return nil, db.Error()
@ -354,9 +354,9 @@ func updateService(s *Service) {
// Delete will remove a service from the database, it will also end the service checking go routine
func (s *Service) Delete() error {
i := s.index()
err := servicesDB().Delete(s)
err := Database(&Service{}).Delete(s)
if err.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error()))
return err.Error()
}
s.Close()
@ -369,7 +369,7 @@ func (s *Service) Delete() error {
// Update will update a service in the database, the service's checking routine can be restarted by passing true
func (s *Service) Update(restart bool) error {
err := servicesDB().Update(&s)
err := Database(&Service{}).Update(&s)
if err.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
return err.Error()
@ -396,7 +396,7 @@ func (s *Service) Update(restart bool) error {
// Create will create a service and insert it into the database
func (s *Service) Create(check bool) (int64, error) {
s.CreatedAt = time.Now().UTC()
db := servicesDB().Create(s)
db := Database(&Service{}).Create(s)
if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error()))
return 0, db.Error()

View File

@ -35,35 +35,35 @@ func ReturnUser(u *types.User) *User {
// CountUsers returns the amount of users
func CountUsers() int64 {
var amount int64
usersDB().Count(&amount)
Database(&User{}).Count(&amount)
return amount
}
// SelectUser returns the User based on the User's ID.
func SelectUser(id int64) (*User, error) {
var user User
err := usersDB().Where("id = ?", id).First(&user)
err := Database(&User{}).Where("id = ?", id).First(&user)
return &user, err.Error()
}
// SelectUsername returns the User based on the User's username
func SelectUsername(username string) (*User, error) {
var user User
res := usersDB().Where("username = ?", username)
res := Database(&User{}).Where("username = ?", username)
err := res.First(&user)
return &user, err.Error()
}
// Delete will remove the User record from the database
func (u *User) Delete() error {
return usersDB().Delete(u).Error()
return Database(&User{}).Delete(u).Error()
}
// Update will update the User's record in database
func (u *User) Update() error {
u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10)
return usersDB().Update(u).Error()
return Database(&User{}).Update(u).Error()
}
// Create will insert a new User into the database
@ -72,7 +72,7 @@ func (u *User) Create() (int64, error) {
u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10)
db := usersDB().Create(u)
db := Database(&User{}).Create(u)
if db.Error() != nil {
return 0, db.Error()
}
@ -86,7 +86,7 @@ func (u *User) Create() (int64, error) {
// SelectAllUsers returns all users
func SelectAllUsers() ([]*User, error) {
var users []*User
db := usersDB().Find(&users)
db := Database(&User{}).Find(&users)
if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error()))
return nil, db.Error()

View File

@ -1,7 +1,6 @@
'use strict';
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlPlugin = require('html-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const helpers = require('./helpers');
const isDev = process.env.NODE_ENV === 'development';
@ -60,8 +59,7 @@ const webpackConfig = {
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlPlugin({ template: 'public/index.html', chunksSortMode: 'dependency' })
new VueLoaderPlugin()
]
};

View File

@ -2,6 +2,7 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlPlugin = require('html-webpack-plugin');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
const helpers = require('./helpers');
const commonConfig = require('./webpack.config.common');
@ -25,7 +26,11 @@ const webpackConfig = merge(commonConfig, {
plugins: [
new webpack.EnvironmentPlugin(environment),
new webpack.HotModuleReplacementPlugin(),
new FriendlyErrorsPlugin()
new FriendlyErrorsPlugin(),
new HtmlPlugin({
template: 'public/index.html',
chunksSortMode: 'dependency'
})
],
devServer: {
compress: true,

View File

@ -2,6 +2,7 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlPlugin = require('html-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
@ -48,7 +49,7 @@ const webpackConfig = merge(commonConfig, {
},
styles: {
test: /\.css$/,
name: 'styles',
name: 'style',
chunks: 'all',
enforce: true
}
@ -59,7 +60,7 @@ const webpackConfig = merge(commonConfig, {
new webpack.EnvironmentPlugin(environment),
new MiniCSSExtractPlugin({
filename: 'css/[name].css',
chunkFilename: 'css/[name].[hash].css'
chunkFilename: 'css/[name].css'
}),
new CompressionPlugin({
filename: '[path].gz[query]',
@ -68,7 +69,13 @@ const webpackConfig = merge(commonConfig, {
threshold: 10240,
minRatio: 0.8
}),
new webpack.HashedModuleIdsPlugin()
new webpack.HashedModuleIdsPlugin(),
new HtmlPlugin({
template: 'public/base.gohtml',
filename: 'base.gohtml',
inject: false,
minify: false
})
]
});

View File

@ -4,12 +4,11 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "rm -rf dist && cross-env NODE_ENV=production webpack",
"build": "rm -rf dist && cross-env NODE_ENV=production webpack --mode production",
"dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8888 --progress",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@babel/polyfill": "~7.2",
"@fortawesome/fontawesome-free-solid": "^5.1.0-3",
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-brands-svg-icons": "^5.12.0",
@ -37,6 +36,7 @@
"@babel/plugin-proposal-json-strings": "~7.2",
"@babel/plugin-syntax-dynamic-import": "~7.2",
"@babel/plugin-syntax-import-meta": "~7.2",
"@babel/polyfill": "~7.2",
"@babel/preset-env": "~7.8.3",
"@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.0",
@ -57,7 +57,7 @@
"eslint-plugin-vue": "~5.1",
"file-loader": "^5.0.2",
"friendly-errors-webpack-plugin": "~1.7",
"html-webpack-plugin": "~3.2",
"html-webpack-plugin": "^4.0.0-beta.11",
"mini-css-extract-plugin": "~0.5",
"node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "~5.0",

View File

@ -0,0 +1,34 @@
{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{Name}}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
<meta name="description" content="{{Description}}">
<base href="{{BasePath}}">
{{if USE_CDN}}
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/favicon.ico">
{{else}}
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<% _.each(htmlWebpackPlugin.tags.headTags, function(headTag) { %>
<%= headTag %> <% }) %>
{{end}}
</head>
<body>
<noscript>
<strong>We're sorry but Statping doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
{{if USE_CDN}}
{{else}}
<% _.each(htmlWebpackPlugin.tags.bodyTags, function(bodyTag) { %>
<%= bodyTag %> <% }) %>
{{end}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,75 @@
<template>
<div class="list-group mt-3 mb-4">
<div v-for="(failure, index) in failures" :key="index" class="mb-2 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">{{failure.issue}}</h5>
<small>{{toLocal(failure.created_at)}}</small>
</div>
<p class="mb-1">{{failure.issue}}</p>
</div>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</nav>
</div>
</template>
<script>
import ServiceChart from "./ServiceChart";
import Api from "../API";
export default {
name: 'ServiceFailures',
components: {ServiceChart},
props: {
service: {
type: Object,
required: true
},
},
data () {
return {
failures: [],
limit: 15,
offset: 0,
}
},
async mounted () {
this.failures = await Api.service_failures(this.service.id, this.now(), this.now(), this.limit, this.offset)
},
methods: {
smallText(s) {
if (s.online) {
return `Online, last checked ${this.ago(s.last_success)}`
} else {
return `Offline, last error: ${s.last_failure.issue} ${this.ago(s.last_failure.created_at)}`
}
},
ago(t1) {
const tm = this.parseTime(t1)
return this.duration(this.$moment().utc(), tm)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<form @submit.prevent="login">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input @keyup="checkForm" type="text" v-model="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input @keyup="checkForm" type="password" v-model="password" name="password" class="form-control" id="password" placeholder="Password">
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<div v-if="error" class="alert alert-danger" role="alert">
Incorrect username or password
</div>
<button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="disabled || loading">
{{loading ? "Loading" : "Sign in"}}
</button>
</div>
</div>
</form>
</template>
<script>
import Api from "../components/API";
export default {
name: 'FormLogin',
data () {
return {
username: "",
password: "",
auth: {},
loading: false,
error: false,
disabled: true
}
},
methods: {
checkForm() {
if (!this.username || !this.password) {
this.disabled = true
} else {
this.disabled = false
}
},
async login () {
this.loading = true
this.error = false
const auth = await Api.login(this.username, this.password)
if (auth.error) {
this.error = true
} else if (auth.token) {
this.auth = Api.saveToken(this.username, auth.token)
await this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard')
}
this.loading = false
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -41,13 +41,6 @@
</div>
</div>
<div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
<div class="col-sm-8">
<input v-model="message.notify_method" type="text" name="notify_method" class="form-control" id="notify_method" value="" placeholder="email">
</div>
</div>
<div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
<div class="col-sm-8">
@ -58,7 +51,14 @@
</div>
</div>
<div class="form-group row">
<div v-if="message.notify" class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
<div class="col-sm-8">
<input v-model="message.notify_method" type="text" name="notify_method" class="form-control" id="notify_method" value="" placeholder="email">
</div>
</div>
<div v-if="message.notify" class="form-group row">
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
<div class="col-sm-8">
<div class="form-inline">

View File

@ -11,7 +11,7 @@
<div class="col-6">
<div class="form-group">
<label>Database Connection</label>
<select v-model="setup.db_connection" class="form-control">
<select @change="canSubmit" v-model="setup.db_connection" class="form-control">
<option value="sqlite">Sqlite</option>
<option value="postgres">Postgres</option>
<option value="mysql">MySQL</option>
@ -19,23 +19,23 @@
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_host">
<label>Host</label>
<input v-model="setup.db_host" type="text" class="form-control" placeholder="localhost">
<input @keyup="canSubmit" v-model="setup.db_host" type="text" class="form-control" placeholder="localhost">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_port">
<label>Database Port</label>
<input v-model="setup.db_port" type="text" class="form-control" placeholder="localhost">
<input @keyup="canSubmit" v-model="setup.db_port" type="text" class="form-control" placeholder="localhost">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_user">
<label>Username</label>
<input v-model="setup.db_user" type="text" class="form-control" placeholder="root">
<input @keyup="canSubmit" v-model="setup.db_user" type="text" class="form-control" placeholder="root">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_password">
<label for="db_password">Password</label>
<input v-model="setup.db_password" type="password" class="form-control" placeholder="password123">
<input @keyup="canSubmit" v-model="setup.db_password" type="password" class="form-control" placeholder="password123">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_database">
<label for="db_database">Database</label>
<input v-model="setup.db_database" type="text" class="form-control" placeholder="Database name">
<input @keyup="canSubmit" v-model="setup.db_database" type="text" class="form-control" placeholder="Database name">
</div>
</div>
@ -44,37 +44,37 @@
<div class="form-group">
<label>Project Name</label>
<input v-model="setup.project" type="text" class="form-control" placeholder="Great Uptime" required>
<input @keyup="canSubmit" v-model="setup.project" type="text" class="form-control" placeholder="Great Uptime" required>
</div>
<div class="form-group">
<label>Project Description</label>
<input v-model="setup.description" type="text" class="form-control" placeholder="Great Uptime">
<input @keyup="canSubmit" v-model="setup.description" type="text" class="form-control" placeholder="Great Uptime">
</div>
<div class="form-group">
<label for="domain_input">Domain URL</label>
<input v-model="setup.domain" type="text" class="form-control" id="domain_input" required>
<input @keyup="canSubmit" v-model="setup.domain" type="text" class="form-control" id="domain_input" required>
</div>
<div class="form-group">
<label>Admin Username</label>
<input v-model="setup.username" type="text" class="form-control" placeholder="admin" required>
<input @keyup="canSubmit" v-model="setup.username" type="text" class="form-control" placeholder="admin" required>
</div>
<div class="form-group">
<label>Admin Password</label>
<input v-model="setup.password" type="password" class="form-control" placeholder="password" required>
<input @keyup="canSubmit" v-model="setup.password" type="password" class="form-control" placeholder="password" required>
</div>
<div class="form-group">
<label>Confirm Admin Password</label>
<input v-model="setup.confirm_password" type="password" class="form-control" placeholder="password" required>
<input @keyup="canSubmit" v-model="setup.confirm_password" type="password" class="form-control" placeholder="password" required>
</div>
<div class="form-group">
<span class="switch">
<input v-model="setup.sample_data" type="checkbox" class="switch" id="switch-normal">
<input @keyup="canSubmit" v-model="setup.sample_data" type="checkbox" class="switch" id="switch-normal">
<label for="switch-normal">Load Sample Data</label>
</span>
</div>
@ -85,7 +85,7 @@
{{error}}
</div>
<button @click.prevent="saveSetup" v-bind:disabled="canSubmit() && loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
<button @click.prevent="saveSetup" v-bind:disabled="disabled || loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
{{loading ? "Loading..." : "Save Settings"}}
</button>
</div>
@ -105,6 +105,7 @@
return {
error: null,
loading: false,
disabled: true,
setup: {
db_connection: "sqlite",
db_host: "",
@ -128,7 +129,7 @@
if (!this.$store.getters.hasPublicData) {
await this.$store.dispatch('loadRequired')
}
this.$router.push('/')
this.$router.push('/')
}
},
mounted() {
@ -136,12 +137,23 @@
},
methods: {
canSubmit() {
if (this.db_connection !== 'sqlite') {
if (!this.db_host || !this.db_port || !this.db_user || !this.db_password || !this.db_database) {
return false
this.error = null
const s = this.setup
if (s.db_connection !== 'sqlite') {
if (!s.db_host || !s.db_port || !s.db_user || !s.db_password || !s.db_database) {
this.disabled = true
return
}
}
return !(!this.project || !this.description || !this.domain || !this.username || !this.password || !this.confirm_password);
if (!s.project || !s.description || !s.domain || !s.username || !s.password || !s.confirm_password) {
this.disabled = true
return
}
if (s.password !== s.confirm_password) {
this.disabled = true
return
}
this.disabled = false
},
async saveSetup() {
this.loading = true

View File

@ -82,7 +82,6 @@
watch: {
in_user() {
const u = this.in_user
delete u.password
this.user = u
}
},

View File

@ -19,12 +19,6 @@
authenticated: false
}
},
async mounted() {
const core = await Api.core()
if (!core.logged_in) {
this.$router.push('/login')
}
},
}
</script>

View File

@ -5,30 +5,7 @@
<img class="col-12 mt-5 mt-md-0" src="/public/banner.png">
</div>
<form @submit.prevent="login">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input type="text" v-model="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" v-model="password" name="password" class="form-control" id="password" placeholder="Password">
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="loading">
{{loading ? "Loading" : "Sign in"}}
</button>
<div v-if="error" class="alert alert-danger" role="alert">
Incorrect username or password
</div>
</div>
</div>
</form>
<FormLogin/>
</div>
</div>
@ -36,34 +13,20 @@
<script>
import Api from "../components/API";
import FormLogin from '../forms/Login';
export default {
name: 'Login',
components: {
FormLogin
},
data () {
return {
username: "",
password: "",
auth: {},
loading: false,
error: false
}
},
methods: {
async login (e) {
this.loading = true
this.error = false
const auth = await Api.login(this.username, this.password)
if (auth.error) {
this.error = true
} else if (auth.token) {
this.auth = Api.saveToken(this.username, auth.token)
await this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard')
}
this.loading = false
}
}
}
</script>

View File

@ -60,16 +60,7 @@
<div class="tab-content">
<div class="tab-pane fade active show">
<div class="list-group mt-3 mb-4">
<div v-for="(failure, index) in failures" :key="index" class="mb-2 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">{{failure.issue}}</h5>
<small>{{failure.created_at | moment("dddd, MMMM Do YYYY")}}</small>
</div>
<p class="mb-1">{{failure.issue}}</p>
</div>
</div>
<ServiceFailures :service="service"/>
</div>
<div class="tab-pane fade" :class="{active: tab === 'incidents'}" id="incidents">
@ -109,6 +100,7 @@
<script>
import Api from "../components/API"
import MessageBlock from '../components/Index/MessageBlock';
import ServiceFailures from '../components/Service/ServiceFailures';
import Checkin from "../forms/Checkin";
const axisOptions = {
@ -138,6 +130,7 @@
export default {
name: 'Service',
components: {
ServiceFailures,
MessageBlock,
Checkin
},
@ -253,7 +246,7 @@ export default {
await this.serviceFailures()
},
async serviceFailures() {
this.failures = await Api.service_failures(this.service.id, 0, 99999999999)
this.failures = await Api.service_failures(this.service.id, this.now() - 3600, this.now(), 15)
},
async chartHits() {
this.data = await Api.service_hits(this.service.id, 0, 99999999999, "hour")

View File

@ -30,23 +30,6 @@
<CoreSettings/>
<h2 class="mt-5">Bulk Import Services</h2>
You can import multiple services based on a CSV file with the format shown on the <a href="https://github.com/hunterlong/statping/wiki/Bulk-Import-Services" target="_blank">Bulk Import Wiki</a>.
<div class="card mt-2">
<div class="card-body">
<form action="settings/bulk_import" method="POST" enctype="multipart/form-data" class="form-inline">
<div class="form-group col-10">
<input type="file" name="file" class="form-control-file" accept=".csv">
</div>
<div class="form-group">
<button type="submit" class="btn btn-outline-success right">Import</button>
</div>
</form>
</div>
</div>
<h2 class="mt-5">Additional Settings</h2>
<div v-if="core.domain !== ''" class="row">
<div class="col-12">
@ -57,6 +40,9 @@
<a href="settings/export" class="btn btn-sm btn-secondary">Export Settings</a>
</div>
</div>
<div v-else>
Insert a domain to view QR code for the mobile app.
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,10 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r)
return
}
http.Redirect(w, r, "/settings", http.StatusSeeOther)
output := apiResponse{
Status: "success",
}
returnJson(output, w, r)
}
func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
@ -99,9 +102,8 @@ type cacheJson struct {
}
func apiCacheHandler(w http.ResponseWriter, r *http.Request) {
cache := CacheStorage
var cacheList []cacheJson
for k, v := range cache.List() {
for k, v := range CacheStorage.List() {
cacheList = append(cacheList, cacheJson{
URL: k,
Expiration: time.Unix(0, v.Expiration).UTC(),
@ -112,8 +114,8 @@ func apiCacheHandler(w http.ResponseWriter, r *http.Request) {
}
func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
CacheStorage.StopRoutine()
CacheStorage = NewStorage()
go CleanRoutine()
output := apiResponse{
Status: "success",
}

View File

@ -35,6 +35,7 @@ func TestResetDatabase(t *testing.T) {
err := core.TmpRecords("handlers.db")
require.Nil(t, err)
require.NotNil(t, core.CoreApp)
require.NotNil(t, core.CoreApp.Config)
}
func TestFailedHTTPServer(t *testing.T) {
@ -43,7 +44,6 @@ func TestFailedHTTPServer(t *testing.T) {
}
func TestSetupRoutes(t *testing.T) {
form := url.Values{}
form.Add("db_host", "")
form.Add("db_user", "")
@ -61,17 +61,17 @@ func TestSetupRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Statping Setup Check",
URL: "/setup",
Name: "Statping Check",
URL: "/api",
Method: "GET",
ExpectedStatus: 303,
ExpectedStatus: 200,
},
{
Name: "Statping Run Setup",
URL: "/setup",
URL: "/api/setup",
Method: "POST",
Body: form.Encode(),
ExpectedStatus: 303,
ExpectedStatus: 200,
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
ExpectedFiles: []string{dir + "/config.yml", dir + "/tmp/" + types.SqliteFilename},
}}
@ -94,19 +94,19 @@ func TestMainApiRoutes(t *testing.T) {
URL: "/api",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"name":"Statping Sample Data","description":"This data is only used to testing"`},
ExpectedContains: []string{`"description":"This data is only used to testing"`},
},
{
Name: "Statping Renew API Keys",
URL: "/api/renew",
Method: "POST",
ExpectedStatus: 303,
ExpectedStatus: 200,
},
{
Name: "Statping Clear Cache",
URL: "/api/clear_cache",
Method: "POST",
ExpectedStatus: 303,
ExpectedStatus: 200,
},
{
Name: "404 Error Page",
@ -129,14 +129,14 @@ func TestApiServiceRoutes(t *testing.T) {
Name: "Statping All Services",
URL: "/api/services",
Method: "GET",
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200,
ExpectedContains: []string{`"id":1,"name":"Google","domain":"https://google.com"`},
},
{
Name: "Statping Service 1",
URL: "/api/services/1",
Method: "GET",
ExpectedContains: []string{`"id":1,"name":"Google","domain":"https://google.com"`},
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200,
},
{
@ -370,7 +370,7 @@ func TestMessagesApiRoutes(t *testing.T) {
URL: "/api/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"id":1,"title":"Routine Downtime"`},
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
Name: "Statping Create Message",
URL: "/api/messages",
@ -394,7 +394,7 @@ func TestMessagesApiRoutes(t *testing.T) {
URL: "/api/messages/1",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"id":1,"title":"Routine Downtime"`},
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
Name: "Statping Update Message",
URL: "/api/messages/1",

View File

@ -1,46 +0,0 @@
// Statping
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statping
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers
import (
"net/http"
"testing"
)
func BenchmarkHandleIndex(b *testing.B) {
//b.ReportAllocs()
//r := request(b, "/")
//for i := 0; i < b.N; i++ {
// rw := httptest.NewRecorder()
// indexHandler(rw, r)
//}
}
func BenchmarkServicesHandlerIndex(b *testing.B) {
//r := request(b, "/")
//for i := 0; i < b.N; i++ {
// rw := httptest.NewRecorder()
// servicesHandler(rw, r)
//}
}
func request(t testing.TB, url string) *http.Request {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
t.Fatal(err)
}
return req
}

View File

@ -15,6 +15,7 @@ type Cacher interface {
List() map[string]Item
Lock()
Unlock()
StopRoutine()
}
// Item is a cached reference
@ -23,17 +24,23 @@ type Item struct {
Expiration int64
}
// CleanRoutine is a go routine to automatically remove expired caches that haven't been hit recently
func CleanRoutine() {
for CacheStorage != nil {
CacheStorage.Lock()
for k, v := range CacheStorage.List() {
if v.Expired() {
CacheStorage.Delete(k)
// cleanRoutine is a go routine to automatically remove expired caches that haven't been hit recently
func cleanRoutine(s *Storage) {
duration := 5 * time.Second
CacheRoutine:
for {
select {
case <-s.running:
break CacheRoutine
case <-time.After(duration):
duration = 5 * time.Second
for k, v := range s.List() {
if v.Expired() {
s.Delete(k)
}
}
}
CacheStorage.Unlock()
time.Sleep(5 * time.Second)
}
}
@ -47,16 +54,24 @@ func (item Item) Expired() bool {
//Storage mecanism for caching strings in memory
type Storage struct {
items map[string]Item
mu *sync.RWMutex
items map[string]Item
mu *sync.RWMutex
running chan bool
}
//NewStorage creates a new in memory CacheStorage
func NewStorage() *Storage {
return &Storage{
items: make(map[string]Item),
mu: &sync.RWMutex{},
storage := &Storage{
items: make(map[string]Item),
mu: &sync.RWMutex{},
running: make(chan bool),
}
go cleanRoutine(storage)
return storage
}
func (s Storage) StopRoutine() {
close(s.running)
}
func (s Storage) Lock() {

View File

@ -98,19 +98,19 @@ func apiThemeSaveHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r)
return
}
if err := source.SaveAsset([]byte(themes.Base), utils.Directory+"/assets/scss/base.scss"); err != nil {
if err := source.SaveAsset([]byte(themes.Base), "scss/base.scss"); err != nil {
sendErrorJson(err, w, r)
return
}
if err := source.SaveAsset([]byte(themes.Variables), utils.Directory+"/assets/scss/variables.scss"); err != nil {
if err := source.SaveAsset([]byte(themes.Variables), "scss/variables.scss"); err != nil {
sendErrorJson(err, w, r)
return
}
if err := source.SaveAsset([]byte(themes.Mobile), utils.Directory+"/assets/scss/mobile.scss"); err != nil {
if err := source.SaveAsset([]byte(themes.Mobile), "scss/mobile.scss"); err != nil {
sendErrorJson(err, w, r)
return
}
if err := source.CompileSASS(utils.Directory); err != nil {
if err := source.CompileSASS(); err != nil {
sendErrorJson(err, w, r)
return
}
@ -126,8 +126,8 @@ func apiThemeCreateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r)
return
}
if err := source.CompileSASS(dir); err != nil {
source.CopyToPublic(source.TmplBox, dir+"/assets/css", "base.css")
if err := source.CompileSASS(); err != nil {
source.CopyToPublic(source.TmplBox, "css", "base.css")
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
}
resetRouter()

View File

@ -1,146 +0,0 @@
package handlers
import (
"github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/require"
"net/url"
"testing"
)
func TestGenericRoutes(t *testing.T) {
form := url.Values{}
form.Add("username", "admin")
form.Add("password", "password123")
form2 := url.Values{}
form2.Add("username", "admin")
form2.Add("password", "wrongpassword")
form3 := url.Values{}
form3.Add("variables", "$background-color: #fcfcfc;")
tests := []HTTPTest{
{
Name: "Statping Index",
URL: "/",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{
`<title>Statping Sample Data Status</title>`,
`<footer>`,
},
},
{
Name: "Statping Incorrect Login",
URL: "/dashboard",
Method: "POST",
Body: form.Encode(),
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
ExpectedContains: []string{"Incorrect login information submitted, try again."},
ExpectedStatus: 200,
},
{
Name: "Dashboard Routes",
URL: "/dashboard",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Dashboard</title>"},
},
{
Name: "Services Routes",
URL: "/services",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Services</title>"},
},
{
Name: "Users Routes",
URL: "/users",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Users</title>"},
},
{
Name: "Messages Routes",
URL: "/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping Messages</title>"},
},
{
Name: "Settings Routes",
URL: "/settings",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Settings</title>"},
},
{
Name: "Logs Routes",
URL: "/logs",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Logs</title>"},
},
{
Name: "Help Routes",
URL: "/help",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Help</title>"},
},
{
Name: "Logout",
URL: "/logout",
Method: "GET",
ExpectedStatus: 303,
},
{
Name: "Prometheus Metrics Routes",
URL: "/metrics",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"statping_total_services 15"},
},
{
Name: "Last Log Line",
URL: "/logs/line",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Export JSON file of all objcts",
URL: "/settings/export",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Export Static Assets",
URL: "/settings/build",
Method: "GET",
ExpectedStatus: 303,
ExpectedFiles: []string{utils.Directory + "/assets/css/base.css"},
},
{
Name: "Save SCSS",
URL: "/settings/css",
Method: "POST",
Body: form3.Encode(),
ExpectedStatus: 303,
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
},
{
Name: "Delete Assets",
URL: "/settings/delete_assets",
Method: "GET",
ExpectedStatus: 303,
},
}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -1,31 +0,0 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestMessageRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Messages",
URL: "/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping Messages</title>`},
},
{
Name: "Message 2",
URL: "/message/2",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | Server Reboot</title>`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -30,7 +30,6 @@ var (
log = utils.Log.WithField("type", "handlers")
)
func staticAssets(src string) http.Handler {
return http.StripPrefix(basePath+src+"/", http.FileServer(http.Dir(utils.Directory+"/assets/"+src)))
}
@ -80,7 +79,6 @@ func Router() *mux.Router {
}
api := r.NewRoute().Subrouter()
//api := mux.NewRouter().StrictSlash(true)
api.Use(apiMiddleware)
// API Routes

View File

@ -1,37 +0,0 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestServiceRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Services",
URL: "/services",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | Services</title>`},
},
{
Name: "Services 2",
URL: "/service/2",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping Github Status</title>`},
}, {
Name: "chart.js index file",
URL: "/charts.js",
Method: "GET",
ExpectedStatus: 200,
},
}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -16,240 +16,10 @@
package handlers
import (
"bytes"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/core/integrations"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
func settingsHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, nil)
}
func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
var err error
form := parseForm(r)
app := core.CoreApp
name := form.Get("project")
if name != "" {
app.Name = name
}
description := form.Get("description")
if description != app.Description {
app.Description = description
}
style := form.Get("style")
if style != app.Style {
app.Style = style
}
footer := form.Get("footer")
if footer != app.Footer.String {
app.Footer = types.NewNullString(footer)
}
domain := form.Get("domain")
if domain != app.Domain {
app.Domain = domain
}
timezone := form.Get("timezone")
timeFloat, _ := strconv.ParseFloat(timezone, 10)
app.Timezone = float32(timeFloat)
app.UpdateNotify = types.NewNullBool(form.Get("update_notify") == "true")
app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on")
core.CoreApp, err = core.UpdateCore(app)
if err != nil {
log.Errorln(fmt.Sprintf("issue updating Core: %v", err.Error()))
}
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
form := parseForm(r)
theme := form.Get("theme")
variables := form.Get("variables")
mobile := form.Get("mobile")
source.SaveAsset([]byte(theme), utils.Directory+"/assets/scss/base.scss")
source.SaveAsset([]byte(variables), utils.Directory+"/assets/scss/variables.scss")
source.SaveAsset([]byte(mobile), utils.Directory+"/assets/scss/mobile.scss")
source.CompileSASS(utils.Directory)
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
dir := utils.Directory
if err := source.CreateAllAssets(dir); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
if err := source.CompileSASS(dir); err != nil {
source.CopyToPublic(source.TmplBox, dir+"/assets/css", "base.css")
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
}
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
if err := source.DeleteAllAssets(utils.Directory); err != nil {
log.Errorln(fmt.Errorf("error deleting all assets %v", err))
}
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
var fileData bytes.Buffer
file, _, err := r.FormFile("file")
if err != nil {
log.Errorln(fmt.Errorf("error bulk import services: %v", err))
w.Write([]byte(err.Error()))
return
}
defer file.Close()
io.Copy(&fileData, file)
data := fileData.String()
for i, line := range strings.Split(strings.TrimSuffix(data, "\n"), "\n")[1:] {
col := strings.Split(line, ",")
newService, err := commaToService(col)
if err != nil {
log.Errorln(fmt.Errorf("issue with row %v: %v", i, err))
continue
}
service := core.ReturnService(newService)
_, err = service.Create(true)
if err != nil {
log.Errorln(fmt.Errorf("cannot create service %v: %v", col[0], err))
continue
}
log.Infoln(fmt.Sprintf("Created new service %v", service.Name))
}
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
type integratorOut struct {
Integrator *types.Integration `json:"integrator"`
Services []*types.Service `json:"services"`
Error error
}
func integratorHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
integratorName := vars["name"]
r.ParseForm()
integrator, err := integrations.Find(integratorName)
if err != nil {
log.Errorln(err)
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Error: err,
}, nil)
return
}
log.Info(r.PostForm)
for _, v := range integrator.Get().Fields {
log.Info(v.Name, v.Value)
}
integrations.SetFields(integrator, r.PostForm)
for _, v := range integrator.Get().Fields {
log.Info(v.Name, v.Value)
}
services, err := integrator.List()
if err != nil {
log.Errorln(err)
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Integrator: integrator.Get(),
Error: err,
}, nil)
return
}
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Integrator: integrator.Get(),
Services: services,
}, nil)
}
// commaToService will convert a CSV comma delimited string slice to a Service type
// this function is used for the bulk import services feature
func commaToService(s []string) (*types.Service, error) {
if len(s) != 17 {
err := fmt.Errorf("does not have the expected amount of %v columns for a service", 16)
return nil, err
}
interval, err := time.ParseDuration(s[4])
if err != nil {
return nil, err
}
timeout, err := time.ParseDuration(s[9])
if err != nil {
return nil, err
}
allowNotifications, err := strconv.ParseBool(s[11])
if err != nil {
return nil, err
}
public, err := strconv.ParseBool(s[12])
if err != nil {
return nil, err
}
verifySsl, err := strconv.ParseBool(s[16])
if err != nil {
return nil, err
}
newService := &types.Service{
Name: s[0],
Domain: s[1],
Expected: types.NewNullString(s[2]),
ExpectedStatus: int(utils.ToInt(s[3])),
Interval: int(utils.ToInt(interval.Seconds())),
Type: s[5],
Method: s[6],
PostData: types.NewNullString(s[7]),
Port: int(utils.ToInt(s[8])),
Timeout: int(utils.ToInt(timeout.Seconds())),
AllowNotifications: types.NewNullBool(allowNotifications),
Public: types.NewNullBool(public),
GroupId: int(utils.ToInt(s[13])),
Headers: types.NewNullString(s[14]),
Permalink: types.NewNullString(s[15]),
VerifySSL: types.NewNullBool(verifySsl),
}
return newService, nil
}
func parseForm(r *http.Request) url.Values {
r.ParseForm()
return r.PostForm

View File

@ -1,31 +0,0 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestUserRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Users",
URL: "/users",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | Users</title>`},
},
{
Name: "User 2",
URL: "/user/2",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | testadmin2</title>`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -80,5 +80,5 @@ func injectDatabase() {
panic(err)
}
db.CreateTable(&notifier.Notification{})
notifier.SetDB(db, float32(-8))
notifier.SetDB(&types.Db{db})
}

View File

@ -23,7 +23,6 @@ import (
"github.com/GeertJohan/go.rice"
"github.com/hunterlong/statping/utils"
"github.com/russross/blackfriday/v2"
"io/ioutil"
"os"
"path/filepath"
)
@ -46,14 +45,14 @@ func HelpMarkdown() string {
}
// CompileSASS will attempt to compile the SASS files into CSS
func CompileSASS(folder string) error {
func CompileSASS() error {
sassBin := os.Getenv("SASS")
if sassBin == "" {
sassBin = "sass"
}
scssFile := fmt.Sprintf("%v/%v", folder, "assets/scss/base.scss")
baseFile := fmt.Sprintf("%v/%v", folder, "assets/css/base.css")
scssFile := fmt.Sprintf("%v/assets/%v", utils.Directory, "scss/base.scss")
baseFile := fmt.Sprintf("%v/assets/%v", utils.Directory, "css/base.css")
log.Infoln(fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile))
command := fmt.Sprintf("%v %v %v", sassBin, scssFile, baseFile)
@ -83,8 +82,10 @@ func UsingAssets(folder string) bool {
} else {
if os.Getenv("USE_ASSETS") == "true" {
log.Infoln("Environment variable USE_ASSETS was found.")
CreateAllAssets(folder)
err := CompileSASS(folder)
if err := CreateAllAssets(folder); err != nil {
log.Warnln(err)
}
err := CompileSASS()
if err != nil {
//CopyToPublic(CssBox, folder+"/css", "base.css")
log.Warnln("Default 'base.css' was insert because SASS did not work.")
@ -97,24 +98,26 @@ func UsingAssets(folder string) bool {
}
// SaveAsset will save an asset to the '/assets/' folder.
func SaveAsset(data []byte, location string) error {
log.Infoln(fmt.Sprintf("Saving %v", location))
err := utils.SaveFile(location, data)
func SaveAsset(data []byte, path string) error {
path = fmt.Sprintf("%s/assets/%s", utils.Directory, path)
log.Infoln(fmt.Sprintf("Saving %v", path))
err := utils.SaveFile(path, data)
if err != nil {
log.Errorln(fmt.Sprintf("Failed to save %v, %v", location, err))
log.Errorln(fmt.Sprintf("Failed to save %v, %v", path, err))
return err
}
return nil
}
// OpenAsset returns a file's contents as a string
func OpenAsset(folder, file string) string {
dat, err := ioutil.ReadFile(folder + "/assets/" + file)
func OpenAsset(path string) string {
path = fmt.Sprintf("%s/assets/%s", utils.Directory, path)
data, err := utils.OpenFile(path)
if err != nil {
log.Errorln(fmt.Sprintf("Failed to open %v, %v", file, err))
log.Errorln(fmt.Sprintf("Failed to open %v, %v", path, err))
return ""
}
return string(dat)
return data
}
// CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory
@ -130,18 +133,19 @@ func CreateAllAssets(folder string) error {
MakePublicFolder(fp(folder, "assets", "files"))
log.Infoln("Inserting scss, css, and javascript files into assets folder")
if err := CopyAllToPublic(TmplBox, fp(folder, "assets")); err != nil {
if err := CopyAllToPublic(TmplBox); err != nil {
log.Errorln(err)
return err
}
CopyToPublic(TmplBox, folder+"/assets", "robots.txt")
CopyToPublic(TmplBox, folder+"/assets", "banner.png")
CopyToPublic(TmplBox, folder+"/assets", "favicon.ico")
CopyToPublic(TmplBox, folder+"/assets/files", "swagger.json")
CopyToPublic(TmplBox, folder+"/assets/files", "postman.json")
CopyToPublic(TmplBox, folder+"/assets/files", "grafana.json")
CopyToPublic(TmplBox, "", "robots.txt")
CopyToPublic(TmplBox, "", "banner.png")
CopyToPublic(TmplBox, "", "favicon.ico")
CopyToPublic(TmplBox, "files", "swagger.json")
CopyToPublic(TmplBox, "files", "postman.json")
CopyToPublic(TmplBox, "files", "grafana.json")
log.Infoln("Compiling CSS from SCSS style...")
err := CompileSASS(utils.Directory)
err := CompileSASS()
log.Infoln("Statping assets have been inserted")
return err
}
@ -158,7 +162,7 @@ func DeleteAllAssets(folder string) error {
}
// CopyAllToPublic will copy all the files in a rice box into a local folder
func CopyAllToPublic(box *rice.Box, folder string) error {
func CopyAllToPublic(box *rice.Box) error {
exclude := map[string]bool{
"base.gohtml": true,
@ -183,24 +187,26 @@ func CopyAllToPublic(box *rice.Box, folder string) error {
if err != nil {
return err
}
filePath := filepath.Join(folder, path)
return SaveAsset(file, filePath)
return SaveAsset(file, path)
})
return err
}
// CopyToPublic will create a file from a rice Box to the '/assets' directory
func CopyToPublic(box *rice.Box, folder, file string) error {
assetFolder := fmt.Sprintf("%v/%v", folder, file)
log.Infoln(fmt.Sprintf("Copying %v to %v", file, assetFolder))
func CopyToPublic(box *rice.Box, path, file string) error {
assetPath := fmt.Sprintf("%v/assets/%v/%v", utils.Directory, path, file)
if path == "" {
assetPath = fmt.Sprintf("%v/assets/%v", utils.Directory, file)
}
log.Infoln(fmt.Sprintf("Copying %v to %v", file, assetPath))
base, err := box.String(file)
if err != nil {
log.Errorln(fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err))
log.Errorln(fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetPath, err))
return err
}
err = ioutil.WriteFile(assetFolder, []byte(base), 0744)
err = utils.SaveFile(assetPath, []byte(base))
if err != nil {
log.Errorln(fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err))
log.Errorln(fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetPath, err))
return err
}
return nil

View File

@ -18,6 +18,7 @@ package source
import (
"github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
@ -42,39 +43,45 @@ func TestCreateAssets(t *testing.T) {
assert.FileExists(t, dir+"/assets/css/base.css")
assert.FileExists(t, dir+"/assets/scss/base.scss")
}
func TestCopyAllToPublic(t *testing.T) {
err := CopyAllToPublic(TmplBox)
require.Nil(t, err)
}
func TestCompileSASS(t *testing.T) {
CompileSASS(dir)
err := CompileSASS()
require.Nil(t, err)
assert.True(t, UsingAssets(dir))
}
func TestSaveAsset(t *testing.T) {
data := []byte("BODY { color: black; }")
asset := SaveAsset(data, dir, "scss/theme.scss")
assert.Nil(t, asset)
err := SaveAsset(data, "scss/theme.scss")
assert.Nil(t, err)
assert.FileExists(t, dir+"/assets/scss/theme.scss")
}
func TestOpenAsset(t *testing.T) {
asset := OpenAsset(dir, "scss/theme.scss")
asset := OpenAsset("scss/theme.scss")
assert.NotEmpty(t, asset)
}
func TestDeleteAssets(t *testing.T) {
assert.True(t, UsingAssets(dir))
assert.Nil(t, DeleteAllAssets(dir))
assert.False(t, UsingAssets(dir))
}
func TestCopyToPluginFailed(t *testing.T) {
assert.Nil(t, DeleteAllAssets(dir))
//assert.Nil(t, DeleteAllAssets(dir))
assert.False(t, UsingAssets(dir))
}
func ExampleSaveAsset() {
data := []byte("alert('helloooo')")
SaveAsset(data, "js", "test.js")
SaveAsset(data, "js/test.js")
}
func ExampleOpenAsset() {
OpenAsset("js", "main.js")
OpenAsset("js/main.js")
}

View File

@ -19,7 +19,7 @@ import (
"time"
)
// SqliteFilename is the name of the SQLlite database file
// SqliteFilename is the name of the SQLlite Db file
const SqliteFilename = "statping.db"
// AllNotifiers contains all the Notifiers loaded

View File

@ -20,7 +20,7 @@ import (
)
// Failure is a failed attempt to check a service. Any a service does not meet the expected requirements,
// a new Failure will be inserted into database.
// a new Failure will be inserted into Db.
type Failure struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Issue string `gorm:"column:issue" json:"issue"`

View File

@ -116,18 +116,18 @@ type Failurer interface {
Fails() ([]*Failure, error)
}
func (it *database) Failures(id int64) Database {
func (it *Db) Failures(id int64) Database {
return it.Model(&Failure{}).Where("service = ?", id).Not("method = 'checkin'").Order("id desc")
}
func (it *database) Fails() ([]*Failure, error) {
func (it *Db) Fails() ([]*Failure, error) {
var fails []*Failure
err := it.Find(&fails)
return fails, err.Error()
}
type database struct {
w *gorm.DB
type Db struct {
Database *gorm.DB
}
// Openw is a drop-in replacement for Open()
@ -138,316 +138,316 @@ func Openw(dialect string, args ...interface{}) (db Database, err error) {
// Wrap wraps gorm.DB in an interface
func Wrap(db *gorm.DB) Database {
return &database{db}
return &Db{db}
}
func (it *database) Close() error {
return it.w.Close()
func (it *Db) Close() error {
return it.Database.Close()
}
func (it *database) DB() *sql.DB {
return it.w.DB()
func (it *Db) DB() *sql.DB {
return it.Database.DB()
}
func (it *database) New() Database {
return Wrap(it.w.New())
func (it *Db) New() Database {
return Wrap(it.Database.New())
}
func (it *database) NewScope(value interface{}) *gorm.Scope {
return it.w.NewScope(value)
func (it *Db) NewScope(value interface{}) *gorm.Scope {
return it.Database.NewScope(value)
}
func (it *database) CommonDB() gorm.SQLCommon {
return it.w.CommonDB()
func (it *Db) CommonDB() gorm.SQLCommon {
return it.Database.CommonDB()
}
func (it *database) Callback() *gorm.Callback {
return it.w.Callback()
func (it *Db) Callback() *gorm.Callback {
return it.Database.Callback()
}
func (it *database) SetLogger(log gorm.Logger) {
it.w.SetLogger(log)
func (it *Db) SetLogger(log gorm.Logger) {
it.Database.SetLogger(log)
}
func (it *database) LogMode(enable bool) Database {
return Wrap(it.w.LogMode(enable))
func (it *Db) LogMode(enable bool) Database {
return Wrap(it.Database.LogMode(enable))
}
func (it *database) SingularTable(enable bool) {
it.w.SingularTable(enable)
func (it *Db) SingularTable(enable bool) {
it.Database.SingularTable(enable)
}
func (it *database) Where(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Where(query, args...))
func (it *Db) Where(query interface{}, args ...interface{}) Database {
return Wrap(it.Database.Where(query, args...))
}
func (it *database) Or(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Or(query, args...))
func (it *Db) Or(query interface{}, args ...interface{}) Database {
return Wrap(it.Database.Or(query, args...))
}
func (it *database) Not(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Not(query, args...))
func (it *Db) Not(query interface{}, args ...interface{}) Database {
return Wrap(it.Database.Not(query, args...))
}
func (it *database) Limit(value int) Database {
return Wrap(it.w.Limit(value))
func (it *Db) Limit(value int) Database {
return Wrap(it.Database.Limit(value))
}
func (it *database) Offset(value int) Database {
return Wrap(it.w.Offset(value))
func (it *Db) Offset(value int) Database {
return Wrap(it.Database.Offset(value))
}
func (it *database) Order(value string, reorder ...bool) Database {
return Wrap(it.w.Order(value, reorder...))
func (it *Db) Order(value string, reorder ...bool) Database {
return Wrap(it.Database.Order(value, reorder...))
}
func (it *database) Select(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Select(query, args...))
func (it *Db) Select(query interface{}, args ...interface{}) Database {
return Wrap(it.Database.Select(query, args...))
}
func (it *database) Omit(columns ...string) Database {
return Wrap(it.w.Omit(columns...))
func (it *Db) Omit(columns ...string) Database {
return Wrap(it.Database.Omit(columns...))
}
func (it *database) Group(query string) Database {
return Wrap(it.w.Group(query))
func (it *Db) Group(query string) Database {
return Wrap(it.Database.Group(query))
}
func (it *database) Having(query string, values ...interface{}) Database {
return Wrap(it.w.Having(query, values...))
func (it *Db) Having(query string, values ...interface{}) Database {
return Wrap(it.Database.Having(query, values...))
}
func (it *database) Joins(query string, args ...interface{}) Database {
return Wrap(it.w.Joins(query, args...))
func (it *Db) Joins(query string, args ...interface{}) Database {
return Wrap(it.Database.Joins(query, args...))
}
func (it *database) Scopes(funcs ...func(*gorm.DB) *gorm.DB) Database {
return Wrap(it.w.Scopes(funcs...))
func (it *Db) Scopes(funcs ...func(*gorm.DB) *gorm.DB) Database {
return Wrap(it.Database.Scopes(funcs...))
}
func (it *database) Unscoped() Database {
return Wrap(it.w.Unscoped())
func (it *Db) Unscoped() Database {
return Wrap(it.Database.Unscoped())
}
func (it *database) Attrs(attrs ...interface{}) Database {
return Wrap(it.w.Attrs(attrs...))
func (it *Db) Attrs(attrs ...interface{}) Database {
return Wrap(it.Database.Attrs(attrs...))
}
func (it *database) Assign(attrs ...interface{}) Database {
return Wrap(it.w.Assign(attrs...))
func (it *Db) Assign(attrs ...interface{}) Database {
return Wrap(it.Database.Assign(attrs...))
}
func (it *database) First(out interface{}, where ...interface{}) Database {
return Wrap(it.w.First(out, where...))
func (it *Db) First(out interface{}, where ...interface{}) Database {
return Wrap(it.Database.First(out, where...))
}
func (it *database) Last(out interface{}, where ...interface{}) Database {
return Wrap(it.w.Last(out, where...))
func (it *Db) Last(out interface{}, where ...interface{}) Database {
return Wrap(it.Database.Last(out, where...))
}
func (it *database) Find(out interface{}, where ...interface{}) Database {
return Wrap(it.w.Find(out, where...))
func (it *Db) Find(out interface{}, where ...interface{}) Database {
return Wrap(it.Database.Find(out, where...))
}
func (it *database) Scan(dest interface{}) Database {
return Wrap(it.w.Scan(dest))
func (it *Db) Scan(dest interface{}) Database {
return Wrap(it.Database.Scan(dest))
}
func (it *database) Row() *sql.Row {
return it.w.Row()
func (it *Db) Row() *sql.Row {
return it.Database.Row()
}
func (it *database) Rows() (*sql.Rows, error) {
return it.w.Rows()
func (it *Db) Rows() (*sql.Rows, error) {
return it.Database.Rows()
}
func (it *database) ScanRows(rows *sql.Rows, result interface{}) error {
return it.w.ScanRows(rows, result)
func (it *Db) ScanRows(rows *sql.Rows, result interface{}) error {
return it.Database.ScanRows(rows, result)
}
func (it *database) Pluck(column string, value interface{}) Database {
return Wrap(it.w.Pluck(column, value))
func (it *Db) Pluck(column string, value interface{}) Database {
return Wrap(it.Database.Pluck(column, value))
}
func (it *database) Count(value interface{}) Database {
return Wrap(it.w.Count(value))
func (it *Db) Count(value interface{}) Database {
return Wrap(it.Database.Count(value))
}
func (it *database) Related(value interface{}, foreignKeys ...string) Database {
return Wrap(it.w.Related(value, foreignKeys...))
func (it *Db) Related(value interface{}, foreignKeys ...string) Database {
return Wrap(it.Database.Related(value, foreignKeys...))
}
func (it *database) FirstOrInit(out interface{}, where ...interface{}) Database {
return Wrap(it.w.FirstOrInit(out, where...))
func (it *Db) FirstOrInit(out interface{}, where ...interface{}) Database {
return Wrap(it.Database.FirstOrInit(out, where...))
}
func (it *database) FirstOrCreate(out interface{}, where ...interface{}) Database {
return Wrap(it.w.FirstOrCreate(out, where...))
func (it *Db) FirstOrCreate(out interface{}, where ...interface{}) Database {
return Wrap(it.Database.FirstOrCreate(out, where...))
}
func (it *database) Update(attrs ...interface{}) Database {
return Wrap(it.w.Update(attrs...))
func (it *Db) Update(attrs ...interface{}) Database {
return Wrap(it.Database.Update(attrs...))
}
func (it *database) Updates(values interface{}, ignoreProtectedAttrs ...bool) Database {
return Wrap(it.w.Updates(values, ignoreProtectedAttrs...))
func (it *Db) Updates(values interface{}, ignoreProtectedAttrs ...bool) Database {
return Wrap(it.Database.Updates(values, ignoreProtectedAttrs...))
}
func (it *database) UpdateColumn(attrs ...interface{}) Database {
return Wrap(it.w.UpdateColumn(attrs...))
func (it *Db) UpdateColumn(attrs ...interface{}) Database {
return Wrap(it.Database.UpdateColumn(attrs...))
}
func (it *database) UpdateColumns(values interface{}) Database {
return Wrap(it.w.UpdateColumns(values))
func (it *Db) UpdateColumns(values interface{}) Database {
return Wrap(it.Database.UpdateColumns(values))
}
func (it *database) Save(value interface{}) Database {
return Wrap(it.w.Save(value))
func (it *Db) Save(value interface{}) Database {
return Wrap(it.Database.Save(value))
}
func (it *database) Create(value interface{}) Database {
return Wrap(it.w.Create(value))
func (it *Db) Create(value interface{}) Database {
return Wrap(it.Database.Create(value))
}
func (it *database) Delete(value interface{}, where ...interface{}) Database {
return Wrap(it.w.Delete(value, where...))
func (it *Db) Delete(value interface{}, where ...interface{}) Database {
return Wrap(it.Database.Delete(value, where...))
}
func (it *database) Raw(sql string, values ...interface{}) Database {
return Wrap(it.w.Raw(sql, values...))
func (it *Db) Raw(sql string, values ...interface{}) Database {
return Wrap(it.Database.Raw(sql, values...))
}
func (it *database) Exec(sql string, values ...interface{}) Database {
return Wrap(it.w.Exec(sql, values...))
func (it *Db) Exec(sql string, values ...interface{}) Database {
return Wrap(it.Database.Exec(sql, values...))
}
func (it *database) Model(value interface{}) Database {
return Wrap(it.w.Model(value))
func (it *Db) Model(value interface{}) Database {
return Wrap(it.Database.Model(value))
}
func (it *database) Table(name string) Database {
return Wrap(it.w.Table(name))
func (it *Db) Table(name string) Database {
return Wrap(it.Database.Table(name))
}
func (it *database) Debug() Database {
return Wrap(it.w.Debug())
func (it *Db) Debug() Database {
return Wrap(it.Database.Debug())
}
func (it *database) Begin() Database {
return Wrap(it.w.Begin())
func (it *Db) Begin() Database {
return Wrap(it.Database.Begin())
}
func (it *database) Commit() Database {
return Wrap(it.w.Commit())
func (it *Db) Commit() Database {
return Wrap(it.Database.Commit())
}
func (it *database) Rollback() Database {
return Wrap(it.w.Rollback())
func (it *Db) Rollback() Database {
return Wrap(it.Database.Rollback())
}
func (it *database) NewRecord(value interface{}) bool {
return it.w.NewRecord(value)
func (it *Db) NewRecord(value interface{}) bool {
return it.Database.NewRecord(value)
}
func (it *database) RecordNotFound() bool {
return it.w.RecordNotFound()
func (it *Db) RecordNotFound() bool {
return it.Database.RecordNotFound()
}
func (it *database) CreateTable(values ...interface{}) Database {
return Wrap(it.w.CreateTable(values...))
func (it *Db) CreateTable(values ...interface{}) Database {
return Wrap(it.Database.CreateTable(values...))
}
func (it *database) DropTable(values ...interface{}) Database {
return Wrap(it.w.DropTable(values...))
func (it *Db) DropTable(values ...interface{}) Database {
return Wrap(it.Database.DropTable(values...))
}
func (it *database) DropTableIfExists(values ...interface{}) Database {
return Wrap(it.w.DropTableIfExists(values...))
func (it *Db) DropTableIfExists(values ...interface{}) Database {
return Wrap(it.Database.DropTableIfExists(values...))
}
func (it *database) HasTable(value interface{}) bool {
return it.w.HasTable(value)
func (it *Db) HasTable(value interface{}) bool {
return it.Database.HasTable(value)
}
func (it *database) AutoMigrate(values ...interface{}) Database {
return Wrap(it.w.AutoMigrate(values...))
func (it *Db) AutoMigrate(values ...interface{}) Database {
return Wrap(it.Database.AutoMigrate(values...))
}
func (it *database) ModifyColumn(column string, typ string) Database {
return Wrap(it.w.ModifyColumn(column, typ))
func (it *Db) ModifyColumn(column string, typ string) Database {
return Wrap(it.Database.ModifyColumn(column, typ))
}
func (it *database) DropColumn(column string) Database {
return Wrap(it.w.DropColumn(column))
func (it *Db) DropColumn(column string) Database {
return Wrap(it.Database.DropColumn(column))
}
func (it *database) AddIndex(indexName string, columns ...string) Database {
return Wrap(it.w.AddIndex(indexName, columns...))
func (it *Db) AddIndex(indexName string, columns ...string) Database {
return Wrap(it.Database.AddIndex(indexName, columns...))
}
func (it *database) AddUniqueIndex(indexName string, columns ...string) Database {
return Wrap(it.w.AddUniqueIndex(indexName, columns...))
func (it *Db) AddUniqueIndex(indexName string, columns ...string) Database {
return Wrap(it.Database.AddUniqueIndex(indexName, columns...))
}
func (it *database) RemoveIndex(indexName string) Database {
return Wrap(it.w.RemoveIndex(indexName))
func (it *Db) RemoveIndex(indexName string) Database {
return Wrap(it.Database.RemoveIndex(indexName))
}
func (it *database) Association(column string) *gorm.Association {
return it.w.Association(column)
func (it *Db) Association(column string) *gorm.Association {
return it.Database.Association(column)
}
func (it *database) Preload(column string, conditions ...interface{}) Database {
return Wrap(it.w.Preload(column, conditions...))
func (it *Db) Preload(column string, conditions ...interface{}) Database {
return Wrap(it.Database.Preload(column, conditions...))
}
func (it *database) Set(name string, value interface{}) Database {
return Wrap(it.w.Set(name, value))
func (it *Db) Set(name string, value interface{}) Database {
return Wrap(it.Database.Set(name, value))
}
func (it *database) InstantSet(name string, value interface{}) Database {
return Wrap(it.w.InstantSet(name, value))
func (it *Db) InstantSet(name string, value interface{}) Database {
return Wrap(it.Database.InstantSet(name, value))
}
func (it *database) Get(name string) (interface{}, bool) {
return it.w.Get(name)
func (it *Db) Get(name string) (interface{}, bool) {
return it.Database.Get(name)
}
func (it *database) SetJoinTableHandler(source interface{}, column string, handler gorm.JoinTableHandlerInterface) {
it.w.SetJoinTableHandler(source, column, handler)
func (it *Db) SetJoinTableHandler(source interface{}, column string, handler gorm.JoinTableHandlerInterface) {
it.Database.SetJoinTableHandler(source, column, handler)
}
func (it *database) AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database {
return Wrap(it.w.AddForeignKey(field, dest, onDelete, onUpdate))
func (it *Db) AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database {
return Wrap(it.Database.AddForeignKey(field, dest, onDelete, onUpdate))
}
func (it *database) AddError(err error) error {
return it.w.AddError(err)
func (it *Db) AddError(err error) error {
return it.Database.AddError(err)
}
func (it *database) GetErrors() (errors []error) {
return it.w.GetErrors()
func (it *Db) GetErrors() (errors []error) {
return it.Database.GetErrors()
}
func (it *database) RowsAffected() int64 {
return it.w.RowsAffected
func (it *Db) RowsAffected() int64 {
return it.Database.RowsAffected
}
func (it *database) Error() error {
return it.w.Error
func (it *Db) Error() error {
return it.Database.Error
}
func (it *database) Hits() ([]*Hit, error) {
func (it *Db) Hits() ([]*Hit, error) {
var hits []*Hit
err := it.Find(&hits)
return hits, err.Error()
}
func (it *database) Since(ago time.Time) Database {
func (it *Db) Since(ago time.Time) Database {
return it.Where("created_at > ?", ago.UTC().Format(TIME))
}
func (it *database) Between(t1 time.Time, t2 time.Time) Database {
func (it *Db) Between(t1 time.Time, t2 time.Time) Database {
return it.Where("created_at BETWEEN ? AND ?", t1.UTC().Format(TIME), t2.UTC().Format(TIME))
}
@ -457,8 +457,8 @@ type DateScan struct {
Value int64 `json:"y"`
}
func (it *database) ToChart() ([]*DateScan, error) {
rows, err := it.w.Rows()
func (it *Db) ToChart() ([]*DateScan, error) {
rows, err := it.Database.Rows()
if err != nil {
return nil, err
}
@ -481,11 +481,11 @@ func (it *database) ToChart() ([]*DateScan, error) {
return data, err
}
func (it *database) QuerySearch(r *http.Request) Database {
func (it *Db) QuerySearch(r *http.Request) Database {
if r == nil {
return it
}
db := it.w
db := it.Database
start := defaultField(r, "start")
end := defaultField(r, "end")
limit := defaultField(r, "limit")

View File

@ -61,6 +61,16 @@ type Service struct {
LastOnline time.Time `gorm:"-" json:"last_success"`
Failures []FailureInterface `gorm:"-" json:"failures,omitempty" scope:"user,admin"`
Checkins []CheckinInterface `gorm:"-" json:"checkins,omitempty" scope:"user,admin"`
Stats Stater `gorm:"-" json:"stats,omitempty"`
}
type Stater interface {
Fetch() *Stats
}
type Stats struct {
Failures uint64 `gorm:"-" json:"failures,omitempty"`
Hits uint64 `gorm:"-" json:"hits,omitempty"`
}
// BeforeCreate for Service will set CreatedAt to UTC

View File

@ -27,7 +27,7 @@ const (
)
var (
NOW = func() time.Time { return time.Now() }()
NOW = func() time.Time { return time.Now().UTC() }()
//HOUR_1_AGO = time.Now().Add(-1 * time.Hour)
//HOUR_24_AGO = time.Now().Add(-24 * time.Hour)
//HOUR_72_AGO = time.Now().Add(-72 * time.Hour)

View File

@ -36,13 +36,13 @@ func (h *Hit) BeforeCreate() (err error) {
return
}
// DbConfig struct is used for the database connection and creates the 'config.yml' file
// DbConfig struct is used for the Db connection and creates the 'config.yml' file
type DbConfig struct {
DbConn string `yaml:"connection" json:"connection"`
DbHost string `yaml:"host" json:"-"`
DbUser string `yaml:"user" json:"-"`
DbPass string `yaml:"password" json:"-"`
DbData string `yaml:"database" json:"-"`
DbData string `yaml:"Db" json:"-"`
DbPort int64 `yaml:"port" json:"-"`
ApiKey string `yaml:"api_key" json:"-"`
ApiSecret string `yaml:"api_secret" json:"-"`

View File

@ -33,7 +33,7 @@ type User struct {
UserInterface `gorm:"-" json:"-"`
}
// UserInterface interfaces the database functions
// UserInterface interfaces the Db functions
type UserInterface interface {
Create() (int64, error)
Update() error

View File

@ -335,7 +335,7 @@ func DurationReadable(d time.Duration) string {
// SaveFile will create a new file with data inside it
// SaveFile("newfile.json", []byte('{"data": "success"}')
func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, 0655)
err := ioutil.WriteFile(filename, data, os.ModePerm)
return err
}