mirror of https://github.com/statping/statping
notifier - moved core
parent
949ba3958a
commit
d0ad55488d
14
cli.go
14
cli.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
"github.com/hunterlong/statup/plugin"
|
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
@ -106,9 +105,8 @@ func RunOnce() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(4, err)
|
utils.Log(4, err)
|
||||||
}
|
}
|
||||||
for _, ser := range core.CoreApp.Services {
|
for _, s := range core.CoreApp.Services {
|
||||||
s := ser.ToService()
|
out := core.ServiceCheck(s.ToService())
|
||||||
out := core.ServiceCheck(s)
|
|
||||||
fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online)
|
fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +127,7 @@ func HelpEcho() {
|
||||||
fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup")
|
fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlugin(plug plugin.PluginActions) {
|
func TestPlugin(plug types.PluginActions) {
|
||||||
defer utils.DeleteFile("./.plugin_test.db")
|
defer utils.DeleteFile("./.plugin_test.db")
|
||||||
RenderBoxes()
|
RenderBoxes()
|
||||||
|
|
||||||
|
@ -160,7 +158,7 @@ func TestPlugin(plug plugin.PluginActions) {
|
||||||
fmt.Println("\n" + BRAKER)
|
fmt.Println("\n" + BRAKER)
|
||||||
fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'")
|
fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'")
|
||||||
fmt.Println(BRAKER)
|
fmt.Println(BRAKER)
|
||||||
core.OnSettingsSaved(core.CoreApp)
|
core.OnSettingsSaved(core.CoreApp.ToCore())
|
||||||
fmt.Println("\n" + BRAKER)
|
fmt.Println("\n" + BRAKER)
|
||||||
fmt.Println(POINT + "Sending 'OnNewService(Service)'")
|
fmt.Println(POINT + "Sending 'OnNewService(Service)'")
|
||||||
core.OnNewService(core.SelectService(2).ToService())
|
core.OnNewService(core.SelectService(2).ToService())
|
||||||
|
@ -180,11 +178,11 @@ func TestPlugin(plug plugin.PluginActions) {
|
||||||
fmt.Println("\n" + BRAKER)
|
fmt.Println("\n" + BRAKER)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FakeSeed(plug plugin.PluginActions) {
|
func FakeSeed(plug types.PluginActions) {
|
||||||
var err error
|
var err error
|
||||||
core.CoreApp = core.NewCore()
|
core.CoreApp = core.NewCore()
|
||||||
|
|
||||||
core.CoreApp.AllPlugins = []plugin.PluginActions{plug}
|
core.CoreApp.AllPlugins = []types.PluginActions{plug}
|
||||||
|
|
||||||
fmt.Printf("\n" + BRAKER)
|
fmt.Printf("\n" + BRAKER)
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,9 @@ func CheckServices() {
|
||||||
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
|
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
|
||||||
for _, ser := range CoreApp.Services {
|
for _, ser := range CoreApp.Services {
|
||||||
s := ser.ToService()
|
s := ser.ToService()
|
||||||
obj := s
|
|
||||||
//go obj.StartCheckins()
|
//go obj.StartCheckins()
|
||||||
obj.StopRoutine = make(chan struct{})
|
s.StopRoutine = make(chan struct{})
|
||||||
go CheckQueue(obj)
|
go CheckQueue(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,7 @@ func (c *Checkin) String() string {
|
||||||
|
|
||||||
func FindCheckin(api string) *types.Checkin {
|
func FindCheckin(api string) *types.Checkin {
|
||||||
for _, ser := range CoreApp.Services {
|
for _, ser := range CoreApp.Services {
|
||||||
s := ser.ToService()
|
for _, c := range ser.ToService().Checkins {
|
||||||
for _, c := range s.Checkins {
|
|
||||||
if c.Api == api {
|
if c.Api == api {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ func LoadUsingEnv() (*types.Config, error) {
|
||||||
DropDatabase()
|
DropDatabase()
|
||||||
CreateDatabase()
|
CreateDatabase()
|
||||||
|
|
||||||
CoreApp = &Core{
|
CoreApp = &Core{Core: &types.Core{
|
||||||
Name: dbConfig.Project,
|
Name: dbConfig.Project,
|
||||||
Description: dbConfig.Description,
|
Description: dbConfig.Description,
|
||||||
Config: "config.yml",
|
Config: "config.yml",
|
||||||
|
@ -93,11 +93,11 @@ func LoadUsingEnv() (*types.Config, error) {
|
||||||
ApiSecret: utils.NewSHA1Hash(16),
|
ApiSecret: utils.NewSHA1Hash(16),
|
||||||
Domain: dbConfig.Domain,
|
Domain: dbConfig.Domain,
|
||||||
MigrationId: time.Now().Unix(),
|
MigrationId: time.Now().Unix(),
|
||||||
}
|
}}
|
||||||
|
|
||||||
CoreApp.DbConnection = dbConfig.DbConn
|
CoreApp.DbConnection = dbConfig.DbConn
|
||||||
|
|
||||||
err := CoreApp.Insert()
|
err := InsertCore(CoreApp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(3, err)
|
utils.Log(3, err)
|
||||||
}
|
}
|
||||||
|
|
43
core/core.go
43
core/core.go
|
@ -3,7 +3,6 @@ package core
|
||||||
import (
|
import (
|
||||||
"github.com/GeertJohan/go.rice"
|
"github.com/GeertJohan/go.rice"
|
||||||
"github.com/hunterlong/statup/notifiers"
|
"github.com/hunterlong/statup/notifiers"
|
||||||
"github.com/hunterlong/statup/plugin"
|
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"os"
|
"os"
|
||||||
|
@ -14,24 +13,8 @@ type PluginJSON types.PluginJSON
|
||||||
type PluginRepos types.PluginRepos
|
type PluginRepos types.PluginRepos
|
||||||
|
|
||||||
type Core struct {
|
type Core struct {
|
||||||
Name string `db:"name" json:"name"`
|
*types.Core
|
||||||
Description string `db:"description" json:"name"`
|
Services []*Service
|
||||||
Config string `db:"config" json:"-"`
|
|
||||||
ApiKey string `db:"api_key" json:"-"`
|
|
||||||
ApiSecret string `db:"api_secret" json:"-"`
|
|
||||||
Style string `db:"style" json:"-"`
|
|
||||||
Footer string `db:"footer" json:"-"`
|
|
||||||
Domain string `db:"domain" json:"domain,omitempty"`
|
|
||||||
Version string `db:"version" json:"version,omitempty"`
|
|
||||||
MigrationId int64 `db:"migration_id" json:"-"`
|
|
||||||
UseCdn bool `db:"use_cdn" json:"-"`
|
|
||||||
Services []*Service `json:"services,omitempty"`
|
|
||||||
Plugins []plugin.Info
|
|
||||||
Repos []PluginJSON
|
|
||||||
AllPlugins []plugin.PluginActions
|
|
||||||
Communications []notifiers.AllNotifiers
|
|
||||||
DbConnection string
|
|
||||||
started time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -53,16 +36,21 @@ func init() {
|
||||||
|
|
||||||
func NewCore() *Core {
|
func NewCore() *Core {
|
||||||
CoreApp = new(Core)
|
CoreApp = new(Core)
|
||||||
CoreApp.started = time.Now()
|
CoreApp.Core = new(types.Core)
|
||||||
|
CoreApp.Started = time.Now()
|
||||||
return CoreApp
|
return CoreApp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) Insert() error {
|
func InsertCore(c *Core) error {
|
||||||
col := DbSession.Collection("core")
|
col := DbSession.Collection("core")
|
||||||
_, err := col.Insert(c)
|
_, err := col.Insert(c.Core)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Core) ToCore() *types.Core {
|
||||||
|
return c.Core
|
||||||
|
}
|
||||||
|
|
||||||
func InitApp() {
|
func InitApp() {
|
||||||
SelectCore()
|
SelectCore()
|
||||||
notifiers.Collections = DbSession.Collection("communication")
|
notifiers.Collections = DbSession.Collection("communication")
|
||||||
|
@ -72,9 +60,9 @@ func InitApp() {
|
||||||
go DatabaseMaintence()
|
go DatabaseMaintence()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) Update() (*Core, error) {
|
func UpdateCore(c *Core) (*Core, error) {
|
||||||
res := DbSession.Collection("core").Find().Limit(1)
|
res := DbSession.Collection("core").Find().Limit(1)
|
||||||
err := res.Update(c)
|
err := res.Update(c.Core)
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +102,7 @@ func (c Core) AllOnline() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SelectLastMigration() (int64, error) {
|
func SelectLastMigration() (int64, error) {
|
||||||
var c *Core
|
var c *types.Core
|
||||||
if DbSession == nil {
|
if DbSession == nil {
|
||||||
return 0, errors.New("Database connection has not been created yet")
|
return 0, errors.New("Database connection has not been created yet")
|
||||||
}
|
}
|
||||||
|
@ -126,17 +114,16 @@ func SelectLastMigration() (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SelectCore() (*Core, error) {
|
func SelectCore() (*Core, error) {
|
||||||
var c *Core
|
var c *types.Core
|
||||||
exists := DbSession.Collection("core").Exists()
|
exists := DbSession.Collection("core").Exists()
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, errors.New("core database has not been setup yet.")
|
return nil, errors.New("core database has not been setup yet.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := DbSession.Collection("core").Find().One(&c)
|
err := DbSession.Collection("core").Find().One(&c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
CoreApp = c
|
CoreApp.Core = c
|
||||||
CoreApp.DbConnection = Configs.Connection
|
CoreApp.DbConnection = Configs.Connection
|
||||||
CoreApp.Version = VERSION
|
CoreApp.Version = VERSION
|
||||||
CoreApp.Services, _ = SelectAllServices()
|
CoreApp.Services, _ = SelectAllServices()
|
||||||
|
|
|
@ -119,7 +119,7 @@ func (c *DbConfig) Save() error {
|
||||||
DropDatabase()
|
DropDatabase()
|
||||||
CreateDatabase()
|
CreateDatabase()
|
||||||
|
|
||||||
newCore := &Core{
|
newCore := &types.Core{
|
||||||
Name: c.Project,
|
Name: c.Project,
|
||||||
Description: c.Description,
|
Description: c.Description,
|
||||||
Config: "config.yml",
|
Config: "config.yml",
|
||||||
|
@ -131,7 +131,7 @@ func (c *DbConfig) Save() error {
|
||||||
col := DbSession.Collection("core")
|
col := DbSession.Collection("core")
|
||||||
_, err = col.Insert(newCore)
|
_, err = col.Insert(newCore)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
CoreApp = newCore
|
CoreApp = &Core{Core: newCore}
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreApp, err = SelectCore()
|
CoreApp, err = SelectCore()
|
||||||
|
@ -204,7 +204,7 @@ func RunDatabaseUpgrades() error {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
CoreApp.MigrationId = currentMigration
|
CoreApp.MigrationId = currentMigration
|
||||||
CoreApp.Update()
|
UpdateCore(CoreApp)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package core
|
||||||
import (
|
import (
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
"github.com/hunterlong/statup/notifiers"
|
"github.com/hunterlong/statup/notifiers"
|
||||||
"github.com/hunterlong/statup/plugin"
|
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
"upper.io/db.v3/lib/sqlbuilder"
|
"upper.io/db.v3/lib/sqlbuilder"
|
||||||
)
|
)
|
||||||
|
@ -28,7 +27,7 @@ func OnFailure(s *types.Service, f FailureData) {
|
||||||
notifiers.OnFailure(s)
|
notifiers.OnFailure(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnSettingsSaved(c *Core) {
|
func OnSettingsSaved(c *types.Core) {
|
||||||
for _, p := range CoreApp.AllPlugins {
|
for _, p := range CoreApp.AllPlugins {
|
||||||
p.OnSettingsSaved(structs.Map(c))
|
p.OnSettingsSaved(structs.Map(c))
|
||||||
}
|
}
|
||||||
|
@ -58,11 +57,11 @@ func OnUpdateService(s *types.Service) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SelectPlugin(name string) plugin.PluginActions {
|
func SelectPlugin(name string) types.PluginActions {
|
||||||
for _, p := range CoreApp.AllPlugins {
|
for _, p := range CoreApp.AllPlugins {
|
||||||
if p.GetInfo().Name == name {
|
if p.GetInfo().Name == name {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return plugin.PluginInfo{}
|
return types.PluginInfo{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,16 @@ func DeleteFailures(u *types.Service) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ser *Service) LimitedFailures() []*types.Failure {
|
func (ser *Service) LimitedFailures() []*Failure {
|
||||||
s := ser.ToService()
|
s := ser.ToService()
|
||||||
var fails []*types.Failure
|
var fails []*types.Failure
|
||||||
|
var failArr []*Failure
|
||||||
col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id").Limit(10)
|
col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id").Limit(10)
|
||||||
col.All(&fails)
|
col.All(&fails)
|
||||||
return fails
|
for _, f := range fails {
|
||||||
|
failArr = append(failArr, MakeFailure(f))
|
||||||
|
}
|
||||||
|
return failArr
|
||||||
}
|
}
|
||||||
|
|
||||||
func reverseFailures(input []*types.Failure) []*types.Failure {
|
func reverseFailures(input []*types.Failure) []*types.Failure {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
S interface{}
|
s *types.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
type Failure struct {
|
type Failure struct {
|
||||||
|
@ -23,10 +23,10 @@ func serviceCol() db.Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SelectService(id int64) *Service {
|
func SelectService(id int64) *Service {
|
||||||
for _, ser := range CoreApp.Services {
|
for _, s := range CoreApp.Services {
|
||||||
s := ser.ToService()
|
ser := s.ToService()
|
||||||
if s.Id == id {
|
if ser.Id == id {
|
||||||
return ser
|
return &Service{ser}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -64,7 +64,7 @@ func (s *Service) AvgTime() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ser *Service) Online24() float32 {
|
func (ser *Service) Online24() float32 {
|
||||||
s := ser.S.(*types.Service)
|
s := ser.ToService()
|
||||||
total, _ := ser.TotalHits()
|
total, _ := ser.TotalHits()
|
||||||
failed, _ := ser.TotalFailures24Hours()
|
failed, _ := ser.TotalFailures24Hours()
|
||||||
if failed == 0 {
|
if failed == 0 {
|
||||||
|
@ -91,12 +91,11 @@ type DateScan struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ToService() *types.Service {
|
func (s *Service) ToService() *types.Service {
|
||||||
return s.S.(*types.Service)
|
return s.s
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(s *types.Service) *Service {
|
func NewService(s *types.Service) *Service {
|
||||||
ser := &Service{s}
|
return &Service{s}
|
||||||
return ser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ser *Service) SmallText() string {
|
func (ser *Service) SmallText() string {
|
||||||
|
@ -105,8 +104,8 @@ func (ser *Service) SmallText() string {
|
||||||
hits, _ := ser.LimitedHits()
|
hits, _ := ser.LimitedHits()
|
||||||
if !s.Online {
|
if !s.Online {
|
||||||
if len(last) > 0 {
|
if len(last) > 0 {
|
||||||
lastFailure := MakeFailure(last[0])
|
lastFailure := MakeFailure(last[0].ToFailure())
|
||||||
return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].CreatedAt.Format("Monday 3:04PM, Jan _2 2006"))
|
return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].ToFailure().CreatedAt.Format("Monday 3:04PM, Jan _2 2006"))
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("%v is currently offline", s.Name)
|
return fmt.Sprintf("%v is currently offline", s.Name)
|
||||||
}
|
}
|
||||||
|
@ -194,10 +193,10 @@ func (ser *Service) AvgUptime() string {
|
||||||
|
|
||||||
func RemoveArray(u *types.Service) []*Service {
|
func RemoveArray(u *types.Service) []*Service {
|
||||||
var srvcs []*Service
|
var srvcs []*Service
|
||||||
for _, ser := range CoreApp.Services {
|
for _, s := range CoreApp.Services {
|
||||||
s := ser.ToService()
|
ser := s.ToService()
|
||||||
if s.Id != u.Id {
|
if ser.Id != u.Id {
|
||||||
srvcs = append(srvcs, ser)
|
srvcs = append(srvcs, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CoreApp.Services = srvcs
|
CoreApp.Services = srvcs
|
||||||
|
@ -241,16 +240,15 @@ func CreateService(u *types.Service) (int64, error) {
|
||||||
}
|
}
|
||||||
u.Id = uuid.(int64)
|
u.Id = uuid.(int64)
|
||||||
u.StopRoutine = make(chan struct{})
|
u.StopRoutine = make(chan struct{})
|
||||||
nn := &Service{u}
|
CoreApp.Services = append(CoreApp.Services, &Service{u})
|
||||||
CoreApp.Services = append(CoreApp.Services, nn)
|
|
||||||
return uuid.(int64), err
|
return uuid.(int64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CountOnline() int {
|
func CountOnline() int {
|
||||||
amount := 0
|
amount := 0
|
||||||
for _, ser := range CoreApp.Services {
|
for _, s := range CoreApp.Services {
|
||||||
s := ser.ToService()
|
ser := s.ToService()
|
||||||
if s.Online {
|
if ser.Online {
|
||||||
amount++
|
amount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,21 @@ func ApiIndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(core.CoreApp)
|
json.NewEncoder(w).Encode(core.CoreApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !isAPIAuthorized(r) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
core.CoreApp.ApiKey = utils.NewSHA1Hash(40)
|
||||||
|
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
|
||||||
|
core.CoreApp, err = core.UpdateCore(core.CoreApp)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log(3, err)
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !isAPIAuthorized(r) {
|
if !isAPIAuthorized(r) {
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
|
@ -33,7 +33,7 @@ func Router() *mux.Router {
|
||||||
r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET")
|
r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET")
|
||||||
r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST")
|
r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST")
|
||||||
r.Handle("/user/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
|
r.Handle("/user/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
|
||||||
r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET")
|
r.Handle("/settings", http.HandlerFunc(SettingsHandler)).Methods("GET")
|
||||||
r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST")
|
r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST")
|
||||||
r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST")
|
r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST")
|
||||||
r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET")
|
r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET")
|
||||||
|
@ -42,6 +42,7 @@ func Router() *mux.Router {
|
||||||
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
|
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
|
||||||
r.Handle("/help", http.HandlerFunc(HelpHandler))
|
r.Handle("/help", http.HandlerFunc(HelpHandler))
|
||||||
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
|
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
|
||||||
|
r.Handle("/api/renew", http.HandlerFunc(ApiRenewHandler))
|
||||||
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
|
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
|
||||||
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
|
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
|
||||||
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")
|
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")
|
||||||
|
|
|
@ -78,6 +78,9 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||||
|
|
||||||
|
fmt.Println(serv.ToService())
|
||||||
|
|
||||||
ExecuteResponse(w, r, "service.html", serv)
|
ExecuteResponse(w, r, "service.html", serv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +97,7 @@ func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "image/svg+xml")
|
w.Header().Set("Content-Type", "image/svg+xml")
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
|
||||||
w.Write(badge)
|
w.Write(badge)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -9,23 +9,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
|
func SettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !IsAuthenticated(r) {
|
if !IsAuthenticated(r) {
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//CoreApp.FetchPluginRepo()
|
|
||||||
|
|
||||||
//var pluginFields []PluginSelect
|
|
||||||
//
|
|
||||||
//for _, p := range allPlugins {
|
|
||||||
// fields := structs.Map(p.GetInfo())
|
|
||||||
//
|
|
||||||
// pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(), fields})
|
|
||||||
//}
|
|
||||||
|
|
||||||
//CoreApp.PluginFields = pluginFields
|
|
||||||
|
|
||||||
ExecuteResponse(w, r, "settings.html", core.CoreApp)
|
ExecuteResponse(w, r, "settings.html", core.CoreApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +44,8 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
core.CoreApp.Domain = domain
|
core.CoreApp.Domain = domain
|
||||||
}
|
}
|
||||||
core.CoreApp.UseCdn = (r.PostForm.Get("enable_cdn") == "on")
|
core.CoreApp.UseCdn = (r.PostForm.Get("enable_cdn") == "on")
|
||||||
core.CoreApp.Update()
|
core.CoreApp, _ = core.UpdateCore(core.CoreApp)
|
||||||
core.OnSettingsSaved(core.CoreApp)
|
core.OnSettingsSaved(core.CoreApp.ToCore())
|
||||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,8 +93,8 @@ func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var2 := r.PostForm.Get("var2")
|
var2 := r.PostForm.Get("var2")
|
||||||
apiKey := r.PostForm.Get("api_key")
|
apiKey := r.PostForm.Get("api_key")
|
||||||
apiSecret := r.PostForm.Get("api_secret")
|
apiSecret := r.PostForm.Get("api_secret")
|
||||||
limits := int64(utils.StringInt(r.PostForm.Get("limits")))
|
limits := int(utils.StringInt(r.PostForm.Get("limits")))
|
||||||
notifer := notifiers.Select(utils.StringInt(notifierId))
|
notifer := notifiers.SelectNotifier(utils.StringInt(notifierId)).Select()
|
||||||
|
|
||||||
if host != "" {
|
if host != "" {
|
||||||
notifer.Host = host
|
notifer.Host = host
|
||||||
|
|
6
main.go
6
main.go
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
"github.com/hunterlong/statup/handlers"
|
"github.com/hunterlong/statup/handlers"
|
||||||
"github.com/hunterlong/statup/plugin"
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -123,8 +123,8 @@ func LoadPlugins(debug bool) {
|
||||||
utils.Log(1, structs.Map(symPlugin))
|
utils.Log(1, structs.Map(symPlugin))
|
||||||
}
|
}
|
||||||
|
|
||||||
var plugActions plugin.PluginActions
|
var plugActions types.PluginActions
|
||||||
plugActions, ok := symPlugin.(plugin.PluginActions)
|
plugActions, ok := symPlugin.(types.PluginActions)
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v", f.Name(), err))
|
utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v", f.Name(), err))
|
||||||
if debug {
|
if debug {
|
||||||
|
|
|
@ -20,7 +20,7 @@ const (
|
||||||
var (
|
var (
|
||||||
emailer *Email
|
emailer *Email
|
||||||
emailArray []string
|
emailArray []string
|
||||||
emailQueue []*types.Email
|
emailQueue []*EmailOutgoing
|
||||||
emailBox *rice.Box
|
emailBox *rice.Box
|
||||||
mailer *gomail.Dialer
|
mailer *gomail.Dialer
|
||||||
)
|
)
|
||||||
|
@ -37,41 +37,35 @@ func init() {
|
||||||
Id: EMAIL_ID,
|
Id: EMAIL_ID,
|
||||||
Method: EMAIL_METHOD,
|
Method: EMAIL_METHOD,
|
||||||
Form: []NotificationForm{{
|
Form: []NotificationForm{{
|
||||||
id: 1,
|
Id: 1,
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Title: "SMTP Host",
|
Title: "SMTP Host",
|
||||||
Placeholder: "Insert your SMTP Host here.",
|
Placeholder: "Insert your SMTP Host here.",
|
||||||
DbField: "Host",
|
DbField: "Host",
|
||||||
}, {
|
}, {
|
||||||
id: 1,
|
Id: 1,
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Title: "SMTP Username",
|
Title: "SMTP Username",
|
||||||
Placeholder: "Insert your SMTP Username here.",
|
Placeholder: "Insert your SMTP Username here.",
|
||||||
DbField: "Username",
|
DbField: "Username",
|
||||||
}, {
|
}, {
|
||||||
id: 1,
|
Id: 1,
|
||||||
Type: "password",
|
Type: "password",
|
||||||
Title: "SMTP Password",
|
Title: "SMTP Password",
|
||||||
Placeholder: "Insert your SMTP Password here.",
|
Placeholder: "Insert your SMTP Password here.",
|
||||||
DbField: "Password",
|
DbField: "Password",
|
||||||
}, {
|
}, {
|
||||||
id: 1,
|
Id: 1,
|
||||||
Type: "number",
|
Type: "number",
|
||||||
Title: "SMTP Port",
|
Title: "SMTP Port",
|
||||||
Placeholder: "Insert your SMTP Port here.",
|
Placeholder: "Insert your SMTP Port here.",
|
||||||
DbField: "Port",
|
DbField: "Port",
|
||||||
}, {
|
}, {
|
||||||
id: 1,
|
Id: 1,
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Title: "Outgoing Email Address",
|
Title: "Outgoing Email Address",
|
||||||
Placeholder: "Insert your Outgoing Email Address",
|
Placeholder: "Insert your Outgoing Email Address",
|
||||||
DbField: "Var1",
|
DbField: "Var1",
|
||||||
}, {
|
|
||||||
id: 1,
|
|
||||||
Type: "number",
|
|
||||||
Title: "Limits per Hour",
|
|
||||||
Placeholder: "How many emails can it send per hour",
|
|
||||||
DbField: "Limits",
|
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -108,7 +102,7 @@ func (u *Email) Init() error {
|
||||||
|
|
||||||
func (u *Email) Test() error {
|
func (u *Email) Test() error {
|
||||||
if u.Enabled {
|
if u.Enabled {
|
||||||
email := &types.Email{
|
email := &EmailOutgoing{
|
||||||
To: "info@socialeck.com",
|
To: "info@socialeck.com",
|
||||||
Subject: "Test Email",
|
Subject: "Test Email",
|
||||||
Template: "message.html",
|
Template: "message.html",
|
||||||
|
@ -124,6 +118,16 @@ type emailMessage struct {
|
||||||
Service *types.Service
|
Service *types.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmailOutgoing struct {
|
||||||
|
To string
|
||||||
|
Subject string
|
||||||
|
Template string
|
||||||
|
From string
|
||||||
|
Data interface{}
|
||||||
|
Source string
|
||||||
|
Sent bool
|
||||||
|
}
|
||||||
|
|
||||||
// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS
|
// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS
|
||||||
func (u *Email) Run() error {
|
func (u *Email) Run() error {
|
||||||
var sentAddresses []string
|
var sentAddresses []string
|
||||||
|
@ -132,16 +136,18 @@ func (u *Email) Run() error {
|
||||||
emailQueue = removeEmail(emailQueue, email)
|
emailQueue = removeEmail(emailQueue, email)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e := email
|
if u.CanSend() {
|
||||||
go func(email *types.Email) {
|
|
||||||
err := u.dialSend(email)
|
err := u.dialSend(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
email.Sent = true
|
email.Sent = true
|
||||||
sentAddresses = append(sentAddresses, email.To)
|
sentAddresses = append(sentAddresses, email.To)
|
||||||
utils.Log(1, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v)", email.Subject, email.To, email.Template, len([]byte(email.Source))))
|
utils.Log(1, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v)", email.Subject, email.To, email.Template, len([]byte(email.Source))))
|
||||||
emailQueue = removeEmail(emailQueue, email)
|
emailQueue = removeEmail(emailQueue, email)
|
||||||
|
u.Log(fmt.Sprintf("Subject: %v to %v", email.Subject, email.To))
|
||||||
|
} else {
|
||||||
|
utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}(e)
|
|
||||||
}
|
}
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
if u.Enabled {
|
if u.Enabled {
|
||||||
|
@ -158,7 +164,7 @@ func (u *Email) OnFailure(s *types.Service) error {
|
||||||
Service: s,
|
Service: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
email := &types.Email{
|
email := &EmailOutgoing{
|
||||||
To: "info@socialeck.com",
|
To: "info@socialeck.com",
|
||||||
Subject: fmt.Sprintf("Service %v is Failing", s.Name),
|
Subject: fmt.Sprintf("Service %v is Failing", s.Name),
|
||||||
Template: "failure.html",
|
Template: "failure.html",
|
||||||
|
@ -190,10 +196,7 @@ func (u *Email) OnSave() error {
|
||||||
|
|
||||||
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
||||||
func (u *Email) Install() error {
|
func (u *Email) Install() error {
|
||||||
|
inDb, err := emailer.Notification.IsInDatabase()
|
||||||
fmt.Println("installing emailer")
|
|
||||||
|
|
||||||
inDb, err := emailer.Notification.isInDatabase()
|
|
||||||
if !inDb {
|
if !inDb {
|
||||||
newNotifer, err := InsertDatabase(u.Notification)
|
newNotifer, err := InsertDatabase(u.Notification)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -205,7 +208,7 @@ func (u *Email) Install() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Email) dialSend(email *types.Email) error {
|
func (u *Email) dialSend(email *EmailOutgoing) error {
|
||||||
fmt.Println("sending dailsend to emailer")
|
fmt.Println("sending dailsend to emailer")
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
m.SetHeader("From", email.From)
|
m.SetHeader("From", email.From)
|
||||||
|
@ -219,7 +222,7 @@ func (u *Email) dialSend(email *types.Email) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendEmail(box *rice.Box, email *types.Email) {
|
func SendEmail(box *rice.Box, email *EmailOutgoing) {
|
||||||
source := EmailTemplate(box, email.Template, email.Data)
|
source := EmailTemplate(box, email.Template, email.Data)
|
||||||
email.Source = source
|
email.Source = source
|
||||||
emailQueue = append(emailQueue, email)
|
emailQueue = append(emailQueue, email)
|
||||||
|
@ -243,8 +246,8 @@ func EmailTemplate(box *rice.Box, tmpl string, data interface{}) string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeEmail(emails []*types.Email, em *types.Email) []*types.Email {
|
func removeEmail(emails []*EmailOutgoing, em *EmailOutgoing) []*EmailOutgoing {
|
||||||
var newArr []*types.Email
|
var newArr []*EmailOutgoing
|
||||||
for _, e := range emails {
|
for _, e := range emails {
|
||||||
if e != em {
|
if e != em {
|
||||||
newArr = append(newArr, e)
|
newArr = append(newArr, e)
|
||||||
|
|
|
@ -10,28 +10,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
AllCommunications []AllNotifiers
|
AllCommunications []types.AllNotifiers
|
||||||
Collections db.Collection
|
Collections db.Collection
|
||||||
|
Logs []*NotificationLog
|
||||||
)
|
)
|
||||||
|
|
||||||
type AllNotifiers interface{}
|
|
||||||
|
|
||||||
func add(c interface{}) {
|
|
||||||
AllCommunications = append(AllCommunications, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Load() []AllNotifiers {
|
|
||||||
utils.Log(1, "Loading notifiers")
|
|
||||||
var notifiers []AllNotifiers
|
|
||||||
for _, comm := range AllCommunications {
|
|
||||||
n := comm.(Notifier)
|
|
||||||
n.Init()
|
|
||||||
notifiers = append(notifiers, n)
|
|
||||||
n.Test()
|
|
||||||
}
|
|
||||||
return notifiers
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
Id int64 `db:"id,omitempty" json:"id"`
|
Id int64 `db:"id,omitempty" json:"id"`
|
||||||
Method string `db:"method" json:"method"`
|
Method string `db:"method" json:"method"`
|
||||||
|
@ -44,7 +27,7 @@ type Notification struct {
|
||||||
ApiKey string `db:"api_key" json:"-"`
|
ApiKey string `db:"api_key" json:"-"`
|
||||||
ApiSecret string `db:"api_secret" json:"-"`
|
ApiSecret string `db:"api_secret" json:"-"`
|
||||||
Enabled bool `db:"enabled" json:"enabled"`
|
Enabled bool `db:"enabled" json:"enabled"`
|
||||||
Limits int64 `db:"limits" json:"-"`
|
Limits int `db:"limits" json:"-"`
|
||||||
Removable bool `db:"removable" json:"-"`
|
Removable bool `db:"removable" json:"-"`
|
||||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
Form []NotificationForm
|
Form []NotificationForm
|
||||||
|
@ -62,14 +45,62 @@ type Notifier interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationForm struct {
|
type NotificationForm struct {
|
||||||
id int64
|
Id int64
|
||||||
Type string
|
Type string
|
||||||
Title string
|
Title string
|
||||||
Placeholder string
|
Placeholder string
|
||||||
DbField string
|
DbField string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notification) isInDatabase() (bool, error) {
|
type NotificationLog struct {
|
||||||
|
Notifier *Notification
|
||||||
|
Message string
|
||||||
|
Time utils.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(c interface{}) {
|
||||||
|
AllCommunications = append(AllCommunications, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() []types.AllNotifiers {
|
||||||
|
utils.Log(1, "Loading notifiers")
|
||||||
|
var notifiers []types.AllNotifiers
|
||||||
|
for _, comm := range AllCommunications {
|
||||||
|
n := comm.(Notifier)
|
||||||
|
n.Init()
|
||||||
|
notifiers = append(notifiers, n)
|
||||||
|
n.Test()
|
||||||
|
}
|
||||||
|
return notifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notification) Log(msg string) {
|
||||||
|
log := &NotificationLog{
|
||||||
|
Notifier: n,
|
||||||
|
Message: msg,
|
||||||
|
Time: utils.Timestamp(time.Now()),
|
||||||
|
}
|
||||||
|
Logs = append(Logs, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notification) Logs() []*NotificationLog {
|
||||||
|
var logs []*NotificationLog
|
||||||
|
for _, v := range Logs {
|
||||||
|
if v.Notifier.Id == n.Id {
|
||||||
|
logs = append(logs, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reverseLogs(logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseLogs(input []*NotificationLog) []*NotificationLog {
|
||||||
|
if len(input) == 0 {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return append(reverseLogs(input[1:]), input[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notification) IsInDatabase() (bool, error) {
|
||||||
return Collections.Find("id", n.Id).Exists()
|
return Collections.Find("id", n.Id).Exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +118,7 @@ func (n *Notification) Update() (*Notification, error) {
|
||||||
|
|
||||||
func InsertDatabase(n *Notification) (int64, error) {
|
func InsertDatabase(n *Notification) (int64, error) {
|
||||||
n.CreatedAt = time.Now()
|
n.CreatedAt = time.Now()
|
||||||
|
n.Limits = 3
|
||||||
newId, err := Collections.Insert(n)
|
newId, err := Collections.Insert(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -94,18 +126,6 @@ func InsertDatabase(n *Notification) (int64, error) {
|
||||||
return newId.(int64), err
|
return newId.(int64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Select(id int64) *Notification {
|
|
||||||
var notifier *Notification
|
|
||||||
for _, n := range AllCommunications {
|
|
||||||
notif := n.(Notifier)
|
|
||||||
notifier = notif.Select()
|
|
||||||
if notifier.Id == id {
|
|
||||||
return notifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return notifier
|
|
||||||
}
|
|
||||||
|
|
||||||
func SelectNotifier(id int64) Notifier {
|
func SelectNotifier(id int64) Notifier {
|
||||||
var notifier Notifier
|
var notifier Notifier
|
||||||
for _, n := range AllCommunications {
|
for _, n := range AllCommunications {
|
||||||
|
@ -118,9 +138,33 @@ func SelectNotifier(id int64) Notifier {
|
||||||
return notifier
|
return notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f Notification) CanSend() bool {
|
||||||
|
if f.SentLastHour() >= f.Limits {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Notification) SentLastHour() int {
|
||||||
|
sent := 0
|
||||||
|
hourAgo := time.Now().Add(-1 * time.Hour)
|
||||||
|
for _, v := range f.Logs() {
|
||||||
|
lastTime := time.Time(v.Time)
|
||||||
|
if lastTime.After(hourAgo) {
|
||||||
|
sent++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sent
|
||||||
|
}
|
||||||
|
|
||||||
func (f NotificationForm) Value() string {
|
func (f NotificationForm) Value() string {
|
||||||
notifier := Select(f.id)
|
noti := SelectNotifier(f.Id)
|
||||||
return notifier.GetValue(f.DbField)
|
return noti.Select().GetValue(f.DbField)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Notification) LimitValue() int64 {
|
||||||
|
notifier, _ := SelectNotification(f.Id)
|
||||||
|
return utils.StringInt(notifier.GetValue("limits"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notification) GetValue(dbField string) string {
|
func (n *Notification) GetValue(dbField string) string {
|
||||||
|
@ -144,6 +188,8 @@ func (n *Notification) GetValue(dbField string) string {
|
||||||
return n.ApiKey
|
return n.ApiKey
|
||||||
case "api_secret":
|
case "api_secret":
|
||||||
return n.ApiSecret
|
return n.ApiSecret
|
||||||
|
case "limits":
|
||||||
|
return utils.IntString(int(n.Limits))
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ func init() {
|
||||||
Method: SLACK_METHOD,
|
Method: SLACK_METHOD,
|
||||||
Host: "https://webhooksurl.slack.com/***",
|
Host: "https://webhooksurl.slack.com/***",
|
||||||
Form: []NotificationForm{{
|
Form: []NotificationForm{{
|
||||||
id: 2,
|
Id: 2,
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Title: "Incoming Webhook Url",
|
Title: "Incoming Webhook Url",
|
||||||
Placeholder: "Insert your Slack webhook URL here.",
|
Placeholder: "Insert your Slack webhook URL here.",
|
||||||
|
@ -84,12 +84,16 @@ func (u *Slack) Run() error {
|
||||||
messageLock.Lock()
|
messageLock.Lock()
|
||||||
slackMessages = uniqueStrings(slackMessages)
|
slackMessages = uniqueStrings(slackMessages)
|
||||||
for _, msg := range slackMessages {
|
for _, msg := range slackMessages {
|
||||||
|
|
||||||
|
if u.CanSend() {
|
||||||
utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook"))
|
utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook"))
|
||||||
client := http.Client{Timeout: 15 * time.Second}
|
client := http.Client{Timeout: 15 * time.Second}
|
||||||
_, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg)))
|
_, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err))
|
utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err))
|
||||||
}
|
}
|
||||||
|
u.Log(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
slackMessages = []string{}
|
slackMessages = []string{}
|
||||||
messageLock.Unlock()
|
messageLock.Unlock()
|
||||||
|
@ -146,7 +150,7 @@ func (u *Slack) OnSave() error {
|
||||||
|
|
||||||
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
||||||
func (u *Slack) Install() error {
|
func (u *Slack) Install() error {
|
||||||
inDb, err := slacker.Notification.isInDatabase()
|
inDb, err := slacker.Notification.IsInDatabase()
|
||||||
if !inDb {
|
if !inDb {
|
||||||
newNotifer, err := InsertDatabase(u.Notification)
|
newNotifer, err := InsertDatabase(u.Notification)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"github.com/hunterlong/statup/types"
|
||||||
"upper.io/db.v3/lib/sqlbuilder"
|
"upper.io/db.v3/lib/sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,48 +21,14 @@ var (
|
||||||
DB sqlbuilder.Database
|
DB sqlbuilder.Database
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetDatabase(database sqlbuilder.Database) {
|
type PluginInfo struct {
|
||||||
DB = database
|
i *types.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginInfo struct {
|
func SetDatabase(database sqlbuilder.Database) {
|
||||||
Info Info
|
DB = database
|
||||||
PluginActions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PluginInfo) Form() string {
|
func (p *PluginInfo) Form() string {
|
||||||
return "okkokokkok"
|
return "okkokokkok"
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginActions interface {
|
|
||||||
GetInfo() Info
|
|
||||||
GetForm() string
|
|
||||||
OnLoad(sqlbuilder.Database)
|
|
||||||
SetInfo(map[string]interface{}) Info
|
|
||||||
Routes() []Routing
|
|
||||||
OnSave(map[string]interface{})
|
|
||||||
OnFailure(map[string]interface{})
|
|
||||||
OnSuccess(map[string]interface{})
|
|
||||||
OnSettingsSaved(map[string]interface{})
|
|
||||||
OnNewUser(map[string]interface{})
|
|
||||||
OnNewService(map[string]interface{})
|
|
||||||
OnUpdatedService(map[string]interface{})
|
|
||||||
OnDeletedService(map[string]interface{})
|
|
||||||
OnInstall(map[string]interface{})
|
|
||||||
OnUninstall(map[string]interface{})
|
|
||||||
OnBeforeRequest(map[string]interface{})
|
|
||||||
OnAfterRequest(map[string]interface{})
|
|
||||||
OnShutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Routing struct {
|
|
||||||
URL string
|
|
||||||
Method string
|
|
||||||
Handler func(http.ResponseWriter, *http.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Info struct {
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
Form string
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,6 +21,11 @@ $(".confirm-btn").on('click', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$(".select-input").on("click", function () {
|
||||||
|
$(this).select();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
var ranVar = false;
|
var ranVar = false;
|
||||||
var ranTheme = false;
|
var ranTheme = false;
|
||||||
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) {
|
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) {
|
||||||
|
|
|
@ -59,26 +59,23 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<h3>Latest Failures</h3>
|
|
||||||
|
|
||||||
{{ range .Services }}
|
{{ range .Services }}
|
||||||
{{ $s := .ToService }}
|
{{ $s := .ToService }}
|
||||||
{{ if .LimitedFailures }}
|
{{ if .LimitedFailures }}
|
||||||
<div class="list-group mt-3">
|
<h4>{{$s.Name}} Failures</h4>
|
||||||
|
<div class="list-group mt-3 mb-4">
|
||||||
{{ range .LimitedFailures }}
|
{{ range .LimitedFailures }}
|
||||||
|
{{ $f := .ToFailure }}
|
||||||
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">{{.ParseError}}</h5>
|
<h5 class="mb-1">{{.ParseError}}</h5>
|
||||||
<small>Reported {{.Ago}}</small>
|
<small>Reported {{.Ago}}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">{{.Issue}}</p>
|
<p class="mb-1">{{$f.Issue}}</p>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -59,14 +59,15 @@
|
||||||
<canvas id="service" width="400" height="120"></canvas>
|
<canvas id="service" width="400" height="120"></canvas>
|
||||||
|
|
||||||
{{ if .LimitedFailures }}
|
{{ if .LimitedFailures }}
|
||||||
<div class="list-group mt-5">
|
<div class="list-group mt-3 mb-4">
|
||||||
{{ range .LimitedFailures }}
|
{{ range .LimitedFailures }}
|
||||||
|
{{ $f := .ToFailure }}
|
||||||
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">{{.ParseError}}</h5>
|
<h5 class="mb-1">{{.ParseError}}</h5>
|
||||||
<small>Reported {{.Ago}}</small>
|
<small>Reported {{.Ago}}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">{{.Issue}}</p>
|
<p class="mb-1">{{$f.Issue}}</p>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -147,7 +148,7 @@
|
||||||
<button type="submit" class="btn btn-success btn-block">Update Service</button>
|
<button type="submit" class="btn btn-success btn-block">Update Service</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<a href="/service/{{ $s.Id }}/delete_failures" class="btn btn-danger btn-block">Delete All Failures</a>
|
<a href="/service/{{ $s.Id }}/delete_failures" class="btn btn-danger btn-block confirm-btn">Delete All Failures</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -156,9 +157,7 @@
|
||||||
|
|
||||||
<div class="col-12 mt-4">
|
<div class="col-12 mt-4">
|
||||||
<h3>Last Response</h3>
|
<h3>Last Response</h3>
|
||||||
|
|
||||||
<textarea rows="8" class="form-control" readonly>{{ $s.LastResponse }}</textarea>
|
<textarea rows="8" class="form-control" readonly>{{ $s.LastResponse }}</textarea>
|
||||||
|
|
||||||
<div class="form-group row mt-2">
|
<div class="form-group row mt-2">
|
||||||
<label for="last_status_code" class="col-sm-3 col-form-label">Status Code</label>
|
<label for="last_status_code" class="col-sm-3 col-form-label">Status Code</label>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="/service/{{$s.Id}}" class="btn btn-primary">View</a>
|
<a href="/service/{{$s.Id}}" class="btn btn-primary">View</a>
|
||||||
<a href="/service/{{$s.Id}}/delete" class="btn btn-danger">Delete</a>
|
<a href="/service/{{$s.Id}}/delete" class="btn btn-danger confirm-btn">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -91,6 +91,7 @@
|
||||||
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<textarea name="expected" class="form-control" id="service_response" rows="3"></textarea>
|
<textarea name="expected" class="form-control" id="service_response" rows="3"></textarea>
|
||||||
|
<small id="emailHelp" class="form-text text-muted">You can insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
|
|
||||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||||
|
|
||||||
|
|
||||||
{{template "nav"}}
|
{{template "nav"}}
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
@ -79,14 +78,19 @@
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
|
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
|
||||||
|
|
||||||
<div class="form-group mt-3">
|
<div class="form-group row mt-3">
|
||||||
<label for="api_key">API Key</label>
|
<label for="api_key" class="col-sm-3 col-form-label">API Key</label>
|
||||||
<input type="text" class="form-control" value="{{ .ApiKey }}" id="api_key" readonly>
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control select-input" value="{{ .ApiKey }}" id="api_key" readonly>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label for="api_secret">API Secret</label>
|
<label for="api_secret" class="col-sm-3 col-form-label">API Secret</label>
|
||||||
<input type="text" class="form-control" value="{{ .ApiSecret }}" id="api_secret" readonly>
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control select-input" value="{{ .ApiSecret }}" id="api_secret" readonly>
|
||||||
|
<small id="emailHelp" class="form-text text-muted">You can <a href="/api/renew">Regenerate API Keys</a> if you need to.</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
@ -122,7 +126,7 @@
|
||||||
|
|
||||||
|
|
||||||
{{ range .Communications }}
|
{{ range .Communications }}
|
||||||
<div class="tab-pane fade" id="v-pills-{{underscore .Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore .Method }}-tab">
|
<div class="tab-pane" id="v-pills-{{underscore .Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore .Method }}-tab">
|
||||||
<form method="POST" action="/settings/notifier/{{ .Id }}">
|
<form method="POST" action="/settings/notifier/{{ .Id }}">
|
||||||
{{range .Form}}
|
{{range .Form}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -130,6 +134,12 @@
|
||||||
<input type="{{.Type}}" name="{{underscore .DbField}}" class="form-control" value="{{ .Value }}" id="{{underscore .Title}}" placeholder="{{.Placeholder}}">
|
<input type="{{.Type}}" name="{{underscore .DbField}}" class="form-control" value="{{ .Value }}" id="{{underscore .Title}}" placeholder="{{.Placeholder}}">
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="text-capitalize" for="limits_per_hour_{{underscore .Method }}">Limits per Hour</label>
|
||||||
|
<input type="number" name="limits" class="form-control" value="{{.LimitValue}}" id="limits_per_hour_{{underscore .Method }}" min="1" max="60" placeholder="How many messages can send per hour">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<span class="switch">
|
<span class="switch">
|
||||||
|
@ -143,6 +153,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{{ if .Logs }}
|
||||||
|
Sent {{.SentLastHour}} out of {{.LimitValue}} in the last hour<br>
|
||||||
|
{{ range .Logs }}
|
||||||
|
<div class="card mt-1">
|
||||||
|
<div class="card-body">
|
||||||
|
{{.Message}}
|
||||||
|
<p class="card-text"><small class="text-muted">Sent {{.Time.Ago}}</small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,72 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
"upper.io/db.v3/lib/sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PluginInfo struct {
|
||||||
|
Info Info
|
||||||
|
PluginActions
|
||||||
|
}
|
||||||
|
|
||||||
|
type Routing struct {
|
||||||
|
URL string
|
||||||
|
Method string
|
||||||
|
Handler func(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Form string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginActions interface {
|
||||||
|
GetInfo() Info
|
||||||
|
GetForm() string
|
||||||
|
OnLoad(sqlbuilder.Database)
|
||||||
|
SetInfo(map[string]interface{}) Info
|
||||||
|
Routes() []Routing
|
||||||
|
OnSave(map[string]interface{})
|
||||||
|
OnFailure(map[string]interface{})
|
||||||
|
OnSuccess(map[string]interface{})
|
||||||
|
OnSettingsSaved(map[string]interface{})
|
||||||
|
OnNewUser(map[string]interface{})
|
||||||
|
OnNewService(map[string]interface{})
|
||||||
|
OnUpdatedService(map[string]interface{})
|
||||||
|
OnDeletedService(map[string]interface{})
|
||||||
|
OnInstall(map[string]interface{})
|
||||||
|
OnUninstall(map[string]interface{})
|
||||||
|
OnBeforeRequest(map[string]interface{})
|
||||||
|
OnAfterRequest(map[string]interface{})
|
||||||
|
OnShutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllNotifiers interface{}
|
||||||
|
|
||||||
|
type Core struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description string `db:"description" json:"name"`
|
||||||
|
Config string `db:"config" json:"-"`
|
||||||
|
ApiKey string `db:"api_key" json:"-"`
|
||||||
|
ApiSecret string `db:"api_secret" json:"-"`
|
||||||
|
Style string `db:"style" json:"-"`
|
||||||
|
Footer string `db:"footer" json:"-"`
|
||||||
|
Domain string `db:"domain" json:"domain,omitempty"`
|
||||||
|
Version string `db:"version" json:"version,omitempty"`
|
||||||
|
MigrationId int64 `db:"migration_id" json:"-"`
|
||||||
|
UseCdn bool `db:"use_cdn" json:"-"`
|
||||||
|
Services []*Service `json:"services,omitempty"`
|
||||||
|
Plugins []Info
|
||||||
|
Repos []PluginJSON
|
||||||
|
AllPlugins []PluginActions
|
||||||
|
Communications []AllNotifiers
|
||||||
|
DbConnection string
|
||||||
|
Started time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Id int64 `db:"id,omitempty" json:"id"`
|
Id int64 `db:"id,omitempty" json:"id"`
|
||||||
Name string `db:"name" json:"name"`
|
Name string `db:"name" json:"name"`
|
||||||
|
@ -67,16 +130,6 @@ type Checkin struct {
|
||||||
Last time.Time `json:"last"`
|
Last time.Time `json:"last"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Email struct {
|
|
||||||
To string
|
|
||||||
Subject string
|
|
||||||
Template string
|
|
||||||
From string
|
|
||||||
Data interface{}
|
|
||||||
Source string
|
|
||||||
Sent bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Connection string `yaml:"connection"`
|
Connection string `yaml:"connection"`
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ararog/timeago"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StringInt(s string) int64 {
|
func StringInt(s string) int64 {
|
||||||
|
@ -12,6 +14,21 @@ func StringInt(s string) int64 {
|
||||||
return int64(num)
|
return int64(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IntString(s int) string {
|
||||||
|
return strconv.Itoa(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timestamp time.Time
|
||||||
|
|
||||||
|
type Timestamper interface {
|
||||||
|
Ago() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Timestamp) Ago() string {
|
||||||
|
got, _ := timeago.TimeAgoWithTime(time.Now(), time.Time(t))
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
func UnderScoreString(str string) string {
|
func UnderScoreString(str string) string {
|
||||||
|
|
||||||
// convert every letter to lower case
|
// convert every letter to lower case
|
||||||
|
|
Loading…
Reference in New Issue