pull/10/head
Hunter Long 2018-06-29 17:57:05 -07:00
parent 4ff0ee5a0d
commit ae92209808
80 changed files with 2077 additions and 2101 deletions

View File

@ -18,7 +18,7 @@ services:
env: env:
global: global:
- VERSION=0.27.4 - VERSION=0.27.6
- DB_HOST=localhost - DB_HOST=localhost
- DB_USER=travis - DB_USER=travis
- DB_PASS= - DB_PASS=
@ -51,8 +51,6 @@ deploy:
notifications: notifications:
email: false email: false
before_install:
before_script: before_script:
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;' - mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
- psql -c 'create database test;' -U postgres - psql -c 'create database test;' -U postgres

View File

@ -2,10 +2,10 @@
# RENDERING CSS # RENDERING CSS
gem install sass gem install sass
sass html/scss/base.scss html/css/base.css sass source/scss/base.scss source/css/base.css
# MIGRATION SQL FILE FOR CURRENT VERSION # MIGRATION SQL FILE FOR CURRENT VERSION
printf "UPDATE core SET version='$VERSION';\n" >> sql/upgrade.sql printf "UPDATE core SET version='$VERSION';\n" >> source/sql/upgrade.sql
# COMPILE SRC INTO BIN # COMPILE SRC INTO BIN
rice embed-go rice embed-go

View File

@ -20,7 +20,6 @@ curl -s -X POST \
-d "$body" \ -d "$body" \
https://api.travis-ci.com/repo/hunterlong%2Fhomebrew-statup/requests https://api.travis-ci.com/repo/hunterlong%2Fhomebrew-statup/requests
if [ "$TRAVIS_BRANCH" == "master" ] if [ "$TRAVIS_BRANCH" == "master" ]
then then
curl -X POST $DOCKER > /dev/null curl -X POST $DOCKER > /dev/null

View File

@ -1,6 +1,6 @@
FROM alpine:latest FROM alpine:latest
ENV VERSION=v0.27.4 ENV VERSION=v0.27.6
RUN apk --no-cache add libstdc++ ca-certificates RUN apk --no-cache add libstdc++ ca-certificates
RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \

View File

@ -1,92 +0,0 @@
package main
import (
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/hunterlong/statup/log"
"io/ioutil"
"os"
"os/exec"
)
var (
useAssets bool
)
func CopyToPublic(box *rice.Box, folder, file string) {
base, err := box.String(file)
if err != nil {
log.Send(2, err)
}
ioutil.WriteFile("assets/"+folder+"/"+file, []byte(base), 0644)
}
func MakePublicFolder(folder string) {
if _, err := os.Stat(folder); os.IsNotExist(err) {
err = os.MkdirAll(folder, 0755)
if err != nil {
log.Send(2, err)
}
}
}
func CompileSASS() {
sassBin := os.Getenv("SASS")
shell := os.Getenv("CMD_FILE")
log.Send(1, fmt.Sprintf("Compiling SASS into /css/base.css..."))
command := fmt.Sprintf("%v %v %v", sassBin, "assets/scss/base.scss", "assets/css/base.css")
testCmd := exec.Command(shell, command)
_, err := testCmd.Output()
if err != nil {
fmt.Println(err)
}
log.Send(1, "SASS Compiling is complete!")
}
func hasAssets() bool {
if _, err := os.Stat("assets"); err == nil {
useAssets = true
return true
} else {
assetEnv := os.Getenv("USE_ASSETS")
if assetEnv == "true" {
CreateAllAssets()
useAssets = true
return true
}
}
return false
}
func SaveAsset(data, file string) {
ioutil.WriteFile("assets/"+file, []byte(data), 0644)
}
func OpenAsset(file string) string {
dat, err := ioutil.ReadFile("assets/" + file)
log.Send(2, err)
return string(dat)
}
func CreateAllAssets() {
log.Send(1, "Creating folder 'assets' in current directory..")
MakePublicFolder("assets")
MakePublicFolder("assets/js")
MakePublicFolder("assets/css")
MakePublicFolder("assets/scss")
MakePublicFolder("assets/emails")
log.Send(1, "Inserting scss, css, emails, and javascript files into assets..")
CopyToPublic(scssBox, "scss", "base.scss")
CopyToPublic(scssBox, "scss", "variables.scss")
CopyToPublic(emailBox, "emails", "error.html")
CopyToPublic(emailBox, "emails", "failure.html")
CopyToPublic(cssBox, "css", "bootstrap.min.css")
CopyToPublic(jsBox, "js", "bootstrap.min.js")
CopyToPublic(jsBox, "js", "Chart.bundle.min.js")
CopyToPublic(jsBox, "js", "jquery-3.3.1.slim.min.js")
CopyToPublic(jsBox, "js", "main.js")
CopyToPublic(jsBox, "js", "setup.js")
log.Send(1, "Compiling CSS from SCSS style...")
CompileSASS()
log.Send(1, "Statup assets have been inserted")
}

76
cli.go
View File

@ -2,9 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"time"
) )
func CatchCLI(args []string) { func CatchCLI(args []string) {
@ -13,65 +13,67 @@ func CatchCLI(args []string) {
fmt.Printf("Statup v%v\n", VERSION) fmt.Printf("Statup v%v\n", VERSION)
case "assets": case "assets":
RenderBoxes() RenderBoxes()
CreateAllAssets() core.CreateAllAssets()
case "sass": case "sass":
CompileSASS() core.CompileSASS()
case "export": case "export":
var err error var err error
fmt.Printf("Statup v%v Exporting Static 'index.html' page...\n", VERSION) fmt.Printf("Statup v%v Exporting Static 'index.html' page...\n", VERSION)
RenderBoxes() RenderBoxes()
configs, err = LoadConfig() core.Configs, err = core.LoadConfig()
if err != nil { if err != nil {
log.Send(3, "config.yml file not found") utils.Log(4, "config.yml file not found")
} }
setupMode = true RunOnce()
mainProcess() indexSource := core.ExportIndexHTML()
time.Sleep(10 * time.Second) err = core.SaveFile("./index.html", []byte(indexSource))
indexSource := ExportIndexHTML()
err = SaveFile("./index.html", []byte(indexSource))
if err != nil { if err != nil {
log.Send(2, err) utils.Log(4, err)
} }
log.Send(1, "Exported Statup index page: 'index.html'") utils.Log(1, "Exported Statup index page: 'index.html'")
case "help": case "help":
HelpEcho() HelpEcho()
case "update": case "update":
fmt.Println("Sorry updating isn't available yet!") fmt.Println("Sorry updating isn't available yet!")
case "run": case "run":
log.Send(1, "Running 1 time and saving to database...") utils.Log(1, "Running 1 time and saving to database...")
var err error RunOnce()
configs, err = LoadConfig()
if err != nil {
log.Send(3, "config.yml file not found")
}
err = DbConnection(configs.Connection)
if err != nil {
log.Send(3, err)
}
core, err = SelectCore()
if err != nil {
fmt.Println("Core database was not found, Statup is not setup yet.")
}
services, err = SelectAllServices()
if err != nil {
log.Send(3, err)
}
for _, s := range services {
out := s.Check()
fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online)
}
fmt.Println("Check is complete.") fmt.Println("Check is complete.")
case "env": case "env":
fmt.Println("Statup Environment Variables") fmt.Println("Statup Environment Variables")
envs, err := godotenv.Read(".env") envs, err := godotenv.Read(".env")
if err != nil { if err != nil {
log.Send(3, "No .env file found in current directory.") utils.Log(4, "No .env file found in current directory.")
} }
for k, e := range envs { for k, e := range envs {
fmt.Printf("%v=%v\n", k, e) fmt.Printf("%v=%v\n", k, e)
} }
default: default:
log.Send(3, "Statup does not have the command you entered.") utils.Log(3, "Statup does not have the command you entered.")
}
}
func RunOnce() {
var err error
core.Configs, err = core.LoadConfig()
if err != nil {
utils.Log(4, "config.yml file not found")
}
err = core.DbConnection(core.Configs.Connection)
if err != nil {
utils.Log(4, err)
}
core.CoreApp, err = core.SelectCore()
if err != nil {
fmt.Println("Core database was not found, Statup is not setup yet.")
}
core.CoreApp.Services, err = core.SelectAllServices()
if err != nil {
utils.Log(4, err)
}
for _, s := range core.CoreApp.Services {
out := s.Check()
fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online)
} }
} }

View File

@ -1,83 +0,0 @@
package main
import (
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/types"
"time"
)
func LoadDefaultCommunications() {
emailer := SelectCommunication(1)
if emailer.Enabled {
LoadMailer(emailer)
go EmailerQueue()
}
}
func LoadComms() {
for _, c := range core.Communications {
if c.Enabled {
}
}
}
func Run(c *types.Communication) {
sample := &types.Email{
To: "info@socialeck.com",
Subject: "Test Email from Statup",
}
AddEmail(sample)
}
func SelectAllCommunications() ([]*types.Communication, error) {
var c []*types.Communication
col := dbSession.Collection("communication").Find()
err := col.All(&c)
core.Communications = c
return c, err
}
func Create(c *types.Communication) (int64, error) {
c.CreatedAt = time.Now()
uuid, err := dbSession.Collection("communication").Insert(c)
if err != nil {
log.Send(3, err)
}
if uuid == nil {
log.Send(2, err)
return 0, err
}
c.Id = uuid.(int64)
if core != nil {
core.Communications = append(core.Communications, c)
}
return uuid.(int64), err
}
func Disable(c *types.Communication) {
c.Enabled = false
Update(c)
}
func Enable(c *types.Communication) {
c.Enabled = true
Update(c)
}
func Update(c *types.Communication) *types.Communication {
col := dbSession.Collection("communication").Find("id", c.Id)
col.Update(c)
return c
}
func SelectCommunication(id int64) *types.Communication {
for _, c := range core.Communications {
if c.Id == id {
return c
}
}
return nil
}

68
core.go
View File

@ -1,68 +0,0 @@
package main
import (
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
)
type Core struct {
Name string `db:"name"`
Description string `db:"description"`
Config string `db:"config"`
ApiKey string `db:"api_key"`
ApiSecret string `db:"api_secret"`
Style string `db:"style"`
Footer string `db:"footer"`
Domain string `db:"domain"`
Version string `db:"version"`
Plugins []plugin.Info
Repos []PluginJSON
PluginFields []PluginSelect
Communications []*types.Communication
OfflineAssets bool
}
func (c *Core) Update() (*Core, error) {
res := dbSession.Collection("core").Find().Limit(1)
res.Update(c)
core = c
return c, nil
}
func (c Core) UsingAssets() bool {
return useAssets
}
func (c Core) SassVars() string {
if !useAssets {
return ""
}
return OpenAsset("scss/variables.scss")
}
func (c Core) BaseSASS() string {
if !useAssets {
return ""
}
return OpenAsset("scss/base.scss")
}
func (c Core) AllOnline() bool {
for _, s := range services {
if !s.Online {
return false
}
}
return true
}
func SelectCore() (*Core, error) {
var c *Core
err := dbSession.Collection("core").Find().One(&c)
if err != nil {
return nil, err
}
core = c
//store = sessions.NewCookieStore([]byte(core.ApiSecret))
return core, err
}

104
core/assets.go Normal file
View File

@ -0,0 +1,104 @@
package core
import (
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/hunterlong/statup/utils"
"io/ioutil"
"os"
"os/exec"
)
func CopyToPublic(box *rice.Box, folder, file string) {
utils.Log(1, fmt.Sprintf("Copying %v to %v...", file, folder))
base, err := box.String(file)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to copy %v to %v, %v.", file, folder, err))
}
ioutil.WriteFile("assets/"+folder+"/"+file, []byte(base), 0644)
}
func MakePublicFolder(folder string) {
utils.Log(1, fmt.Sprintf("Creating folder '%v' in current directory...", folder))
if _, err := os.Stat(folder); os.IsNotExist(err) {
err = os.MkdirAll(folder, 0755)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to created %v directory, %v", folder, err))
}
}
}
func CompileSASS() error {
sassBin := os.Getenv("SASS")
shell := os.Getenv("CMD_FILE")
utils.Log(1, fmt.Sprintf("Compiling SASS into /css/base.css..."))
command := fmt.Sprintf("%v %v %v", sassBin, "assets/scss/base.scss", "assets/css/base.css")
testCmd := exec.Command(shell, command)
_, err := testCmd.Output()
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to compile assets with SASS %v", err))
utils.Log(3, fmt.Sprintf("SASS: %v | CMD_FILE:%v", sassBin, shell))
return err
}
utils.Log(1, "SASS Compiling is complete!")
return err
}
func HasAssets() bool {
if _, err := os.Stat("assets"); err == nil {
utils.Log(1, "Assets folder was found!")
UsingAssets = true
return true
} else {
assetEnv := os.Getenv("USE_ASSETS")
if assetEnv == "true" {
utils.Log(1, "Environment variable USE_ASSETS was found.")
CreateAllAssets()
UsingAssets = true
return true
}
}
return false
}
func SaveAsset(data, file string) {
utils.Log(1, fmt.Sprintf("Saving %v into assets folder", file))
err := ioutil.WriteFile("assets/"+file, []byte(data), 0644)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to save %v, %v", file, err))
}
}
func OpenAsset(file string) string {
dat, err := ioutil.ReadFile("assets/" + file)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to open %v, %v", file, err))
return ""
}
return string(dat)
}
func CreateAllAssets() {
utils.Log(1, "Dump Statup assets into current directory...")
MakePublicFolder("assets")
MakePublicFolder("assets/js")
MakePublicFolder("assets/css")
MakePublicFolder("assets/scss")
MakePublicFolder("assets/emails")
utils.Log(1, "Inserting scss, css, emails, and javascript files into assets..")
CopyToPublic(ScssBox, "scss", "base.scss")
CopyToPublic(ScssBox, "scss", "variables.scss")
CopyToPublic(EmailBox, "emails", "error.html")
CopyToPublic(EmailBox, "emails", "failure.html")
CopyToPublic(CssBox, "css", "bootstrap.min.css")
CopyToPublic(JsBox, "js", "bootstrap.min.js")
CopyToPublic(JsBox, "js", "Chart.bundle.min.js")
CopyToPublic(JsBox, "js", "jquery-3.3.1.slim.min.js")
CopyToPublic(JsBox, "js", "main.js")
CopyToPublic(JsBox, "js", "setup.js")
utils.Log(1, "Compiling CSS from SCSS style...")
err := CompileSASS()
if err == nil {
utils.Log(1, "Statup assets have been inserted")
}
}

View File

@ -1,18 +1,21 @@
package main package core
import ( import (
"fmt" "fmt"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"regexp" "regexp"
"time" "time"
) )
type FailureData types.FailureData
func CheckServices() { func CheckServices() {
services, _ = SelectAllServices() CoreApp.Services, _ = SelectAllServices()
log.Send(1, fmt.Sprintf("Loaded %v Services", len(services))) utils.Log(1, fmt.Sprintf("Loaded %v Services", len(CoreApp.Services)))
for _, v := range services { for _, v := range CoreApp.Services {
obj := v obj := v
go obj.StartCheckins() go obj.StartCheckins()
go obj.CheckQueue() go obj.CheckQueue()
@ -26,7 +29,7 @@ func (s *Service) CheckQueue() {
s.Interval = 1 s.Interval = 1
} }
msg := fmt.Sprintf("Service: %v | Online: %v | Latency: %0.0fms", s.Name, s.Online, (s.Latency * 1000)) msg := fmt.Sprintf("Service: %v | Online: %v | Latency: %0.0fms", s.Name, s.Online, (s.Latency * 1000))
log.Send(0, msg) utils.Log(1, msg)
time.Sleep(time.Duration(s.Interval) * time.Second) time.Sleep(time.Duration(s.Interval) * time.Second)
} }
@ -80,17 +83,13 @@ func (s *Service) Record(response *http.Response) {
OnSuccess(s) OnSuccess(s)
} }
type FailureData struct {
Issue string
}
func (s *Service) Failure(issue string) { func (s *Service) Failure(issue string) {
s.Online = false s.Online = false
data := FailureData{ data := FailureData{
Issue: issue, Issue: issue,
} }
log.Send(1, fmt.Sprintf("Service %v Failing: %v", s.Name, issue)) utils.Log(2, fmt.Sprintf("Service %v Failing: %v", s.Name, issue))
s.CreateFailure(data) s.CreateFailure(data)
SendFailureEmail(s) //SendFailureEmail(s)
OnFailure(s) OnFailure(s)
} }

View File

@ -1,25 +1,33 @@
package main package core
import ( import (
"fmt" "fmt"
"github.com/ararog/timeago" "github.com/ararog/timeago"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"time" "time"
) )
type Checkin struct { type Checkin types.Checkin
Id int `db:"id,omitempty"`
Service int64 `db:"service"` func (c *Checkin) String() string {
Interval int64 `db:"check_interval"` return c.Api
Api string `db:"api"` }
CreatedAt time.Time `db:"created_at"`
Hits int64 `json:"hits"` func FindCheckin(api string) *Checkin {
Last time.Time `json:"last"` for _, s := range CoreApp.Services {
for _, c := range s.Checkins {
if c.Api == api {
return c
}
}
}
return nil
} }
func (s *Service) SelectAllCheckins() []*Checkin { func (s *Service) SelectAllCheckins() []*Checkin {
var checkins []*Checkin var checkins []*Checkin
col := dbSession.Collection("checkins").Find("service", s.Id).OrderBy("-id") col := DbSession.Collection("checkins").Find("service", s.Id).OrderBy("-id")
col.All(&checkins) col.All(&checkins)
s.Checkins = checkins s.Checkins = checkins
return checkins return checkins
@ -27,9 +35,9 @@ func (s *Service) SelectAllCheckins() []*Checkin {
func (u *Checkin) Create() (int64, error) { func (u *Checkin) Create() (int64, error) {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
uuid, err := dbSession.Collection("checkins").Insert(u) uuid, err := DbSession.Collection("checkins").Insert(u)
if uuid == nil { if uuid == nil {
log.Send(2, err) utils.Log(2, err)
return 0, err return 0, err
} }
fmt.Println("new checkin: ", uuid) fmt.Println("new checkin: ", uuid)
@ -38,7 +46,7 @@ func (u *Checkin) Create() (int64, error) {
func SelectCheckinApi(api string) *Checkin { func SelectCheckinApi(api string) *Checkin {
var checkin *Checkin var checkin *Checkin
dbSession.Collection("checkins").Find("api", api).One(&checkin) DbSession.Collection("checkins").Find("api", api).One(&checkin)
return checkin return checkin
} }
@ -70,17 +78,6 @@ func (f *Checkin) Ago() string {
return got return got
} }
func FindCheckin(api string) *Checkin {
for _, s := range services {
for _, c := range s.Checkins {
if c.Api == api {
return c
}
}
}
return nil
}
func (c *Checkin) Run() { func (c *Checkin) Run() {
if c.Interval == 0 { if c.Interval == 0 {
return return
@ -104,7 +101,7 @@ func (s *Service) StartCheckins() {
} }
func CheckinProcess() { func CheckinProcess() {
for _, s := range services { for _, s := range CoreApp.Services {
for _, c := range s.Checkins { for _, c := range s.Checkins {
checkin := c checkin := c
go checkin.Run() go checkin.Run()

85
core/communication.go Normal file
View File

@ -0,0 +1,85 @@
package core
import (
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"time"
)
type Communication types.Communication
func LoadDefaultCommunications() {
emailer := SelectCommunication(1)
if emailer.Enabled {
//LoadMailer(emailer)
//go EmailerQueue()
}
}
func LoadComms() {
for _, c := range CoreApp.Communications {
if c.Enabled {
}
}
}
func Run(c *Communication) {
//sample := &Email{
// To: "info@socialeck.com",
// Subject: "Test Email from Statup",
//}
//AddEmail(sample)
}
func SelectAllCommunications() ([]*Communication, error) {
var c []*Communication
col := DbSession.Collection("communication").Find()
err := col.All(&c)
CoreApp.Communications = c
return c, err
}
func Create(c *Communication) (int64, error) {
c.CreatedAt = time.Now()
uuid, err := DbSession.Collection("communication").Insert(c)
if err != nil {
utils.Log(3, err)
}
if uuid == nil {
utils.Log(2, err)
return 0, err
}
c.Id = uuid.(int64)
if CoreApp != nil {
CoreApp.Communications = append(CoreApp.Communications, c)
}
return uuid.(int64), err
}
func Disable(c *Communication) {
c.Enabled = false
Update(c)
}
func Enable(c *Communication) {
c.Enabled = true
Update(c)
}
func Update(c *Communication) *Communication {
col := DbSession.Collection("communication").Find("id", c.Id)
col.Update(c)
return c
}
func SelectCommunication(id int64) *Communication {
for _, c := range CoreApp.Communications {
if c.Id == id {
return c
}
}
return nil
}

20
core/configs.go Normal file
View File

@ -0,0 +1,20 @@
package core
import (
"github.com/go-yaml/yaml"
"github.com/hunterlong/statup/types"
"io/ioutil"
)
type Config types.Config
func LoadConfig() (*Config, error) {
var config Config
file, err := ioutil.ReadFile("config.yml")
if err != nil {
return nil, err
}
err = yaml.Unmarshal(file, &config)
Configs = &config
return &config, err
}

91
core/core.go Normal file
View File

@ -0,0 +1,91 @@
package core
import (
"github.com/GeertJohan/go.rice"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
)
type PluginJSON types.PluginJSON
type PluginRepos types.PluginRepos
type Core struct {
Name string `db:"name"`
Description string `db:"description"`
Config string `db:"config"`
ApiKey string `db:"api_key"`
ApiSecret string `db:"api_secret"`
Style string `db:"style"`
Footer string `db:"footer"`
Domain string `db:"domain"`
Version string `db:"version"`
Services []*Service
Plugins []plugin.Info
Repos []PluginJSON
//PluginFields []PluginSelect
Communications []*Communication
OfflineAssets bool
}
var (
Configs *Config
CoreApp *Core
SqlBox *rice.Box
CssBox *rice.Box
ScssBox *rice.Box
JsBox *rice.Box
TmplBox *rice.Box
EmailBox *rice.Box
SetupMode bool
AllPlugins []plugin.PluginActions
UsingAssets bool
)
func init() {
CoreApp = new(Core)
}
func (c *Core) Update() (*Core, error) {
res := DbSession.Collection("core").Find().Limit(1)
res.Update(c)
CoreApp = c
return c, nil
}
func (c Core) UsingAssets() bool {
return UsingAssets
}
func (c Core) SassVars() string {
if !UsingAssets {
return ""
}
return OpenAsset("scss/variables.scss")
}
func (c Core) BaseSASS() string {
if !UsingAssets {
return ""
}
return OpenAsset("scss/base.scss")
}
func (c Core) AllOnline() bool {
for _, s := range CoreApp.Services {
if !s.Online {
return false
}
}
return true
}
func SelectCore() (*Core, error) {
var c *Core
err := DbSession.Collection("core").Find().One(&c)
if err != nil {
return nil, err
}
CoreApp = c
//store = sessions.NewCookieStore([]byte(core.ApiSecret))
return CoreApp, err
}

188
core/database.go Normal file
View File

@ -0,0 +1,188 @@
package core
import (
"fmt"
"github.com/go-yaml/yaml"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"os"
"strings"
"time"
"upper.io/db.v3"
"upper.io/db.v3/lib/sqlbuilder"
"upper.io/db.v3/mysql"
"upper.io/db.v3/postgresql"
"upper.io/db.v3/sqlite"
)
var (
dbServer string
sqliteSettings sqlite.ConnectionURL
postgresSettings postgresql.ConnectionURL
mysqlSettings mysql.ConnectionURL
DbSession sqlbuilder.Database
)
type DbConfig types.DbConfig
func DbConnection(dbType string) error {
var err error
if dbType == "sqlite" {
sqliteSettings = sqlite.ConnectionURL{
Database: "statup.db",
}
DbSession, err = sqlite.Open(sqliteSettings)
if err != nil {
return err
}
} else if dbType == "mysql" {
if Configs.Port == "" {
Configs.Port = "3306"
}
mysqlSettings = mysql.ConnectionURL{
Database: Configs.Database,
Host: Configs.Host,
User: Configs.User,
Password: Configs.Password,
}
DbSession, err = mysql.Open(mysqlSettings)
if err != nil {
return err
}
} else {
if Configs.Port == "" {
Configs.Port = "5432"
}
host := fmt.Sprintf("%v:%v", Configs.Host, Configs.Port)
postgresSettings = postgresql.ConnectionURL{
Database: Configs.Database,
Host: host,
User: Configs.User,
Password: Configs.Password,
}
DbSession, err = postgresql.Open(postgresSettings)
if err != nil {
return err
}
}
//dbSession.SetLogging(true)
dbServer = dbType
OnLoad(DbSession)
return err
}
func DatabaseMaintence() {
defer DatabaseMaintence()
utils.Log(1, "Checking for database records older than 7 days...")
since := time.Now().AddDate(0, 0, -7)
DeleteAllSince("failures", since)
DeleteAllSince("hits", since)
time.Sleep(60 * time.Minute)
}
func DeleteAllSince(table string, date time.Time) {
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
_, err := DbSession.Exec(db.Raw(sql))
if err != nil {
utils.Log(2, err)
}
}
func (c *DbConfig) Save() error {
var err error
config, err := os.Create("config.yml")
if err != nil {
utils.Log(2, err)
return err
}
data, err := yaml.Marshal(c)
if err != nil {
utils.Log(2, err)
return err
}
config.WriteString(string(data))
config.Close()
Configs, err = LoadConfig()
if err != nil {
utils.Log(2, err)
return err
}
err = DbConnection(Configs.Connection)
if err != nil {
utils.Log(2, err)
return err
}
DropDatabase()
CreateDatabase()
newCore := Core{
Name: c.Project,
Description: c.Description,
Config: "config.yml",
ApiKey: utils.NewSHA1Hash(5),
ApiSecret: utils.NewSHA1Hash(10),
Domain: c.Domain,
}
col := DbSession.Collection("core")
_, err = col.Insert(newCore)
return err
}
func RunDatabaseUpgrades() {
utils.Log(1, "Running Database Upgrade from 'upgrade.sql'...")
upgrade, _ := SqlBox.String("upgrade.sql")
requests := strings.Split(upgrade, ";")
for _, request := range requests {
_, err := DbSession.Exec(db.Raw(request + ";"))
if err != nil {
utils.Log(2, err)
}
}
utils.Log(1, "Database Upgraded")
}
func DropDatabase() {
fmt.Println("Dropping Tables...")
down, _ := SqlBox.String("down.sql")
requests := strings.Split(down, ";")
for _, request := range requests {
_, err := DbSession.Exec(request)
if err != nil {
utils.Log(2, err)
}
}
}
func CreateDatabase() {
fmt.Println("Creating Tables...")
sql := "postgres_up.sql"
if dbServer == "mysql" {
sql = "mysql_up.sql"
} else if dbServer == "sqlite3" {
sql = "sqlite_up.sql"
}
up, _ := SqlBox.String(sql)
requests := strings.Split(up, ";")
for _, request := range requests {
_, err := DbSession.Exec(request)
if err != nil {
utils.Log(2, err)
}
}
//secret := NewSHA1Hash()
//db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan()
fmt.Println("Database Created")
//SampleData()
}
func (c *DbConfig) Clean() *DbConfig {
if os.Getenv("DB_PORT") != "" {
if c.DbConn == "postgres" {
c.DbHost = c.DbHost + ":" + os.Getenv("DB_PORT")
}
}
return c
}

View File

@ -1,4 +1,4 @@
package main package core
import ( import (
"github.com/fatih/structs" "github.com/fatih/structs"
@ -7,55 +7,55 @@ import (
) )
func OnLoad(db sqlbuilder.Database) { func OnLoad(db sqlbuilder.Database) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnLoad(db) p.OnLoad(db)
} }
} }
func OnSuccess(s *Service) { func OnSuccess(s *Service) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnSuccess(structs.Map(s)) p.OnSuccess(structs.Map(s))
} }
} }
func OnFailure(s *Service) { func OnFailure(s *Service) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnFailure(structs.Map(s)) p.OnFailure(structs.Map(s))
} }
} }
func OnSettingsSaved(c *Core) { func OnSettingsSaved(c *Core) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnSettingsSaved(structs.Map(c)) p.OnSettingsSaved(structs.Map(c))
} }
} }
func OnNewUser(u *User) { func OnNewUser(u *User) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnNewUser(structs.Map(u)) p.OnNewUser(structs.Map(u))
} }
} }
func OnNewService(s *Service) { func OnNewService(s *Service) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnNewService(structs.Map(s)) p.OnNewService(structs.Map(s))
} }
} }
func OnDeletedService(s *Service) { func OnDeletedService(s *Service) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnDeletedService(structs.Map(s)) p.OnDeletedService(structs.Map(s))
} }
} }
func OnUpdateService(s *Service) { func OnUpdateService(s *Service) {
for _, p := range allPlugins { for _, p := range AllPlugins {
p.OnUpdatedService(structs.Map(s)) p.OnUpdatedService(structs.Map(s))
} }
} }
func SelectPlugin(name string) plugin.PluginActions { func SelectPlugin(name string) plugin.PluginActions {
for _, p := range allPlugins { for _, p := range AllPlugins {
if p.GetInfo().Name == name { if p.GetInfo().Name == name {
return p return p
} }

View File

@ -1,20 +1,20 @@
package main package core
import ( import (
"bytes" "bytes"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/utils"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
) )
func ExportIndexHTML() string { func ExportIndexHTML() string {
core.OfflineAssets = true CoreApp.OfflineAssets = true
out := index{*core, services} //out := index{*CoreApp, CoreApp.Services}
nav, _ := tmplBox.String("nav.html") nav, _ := TmplBox.String("nav.html")
footer, _ := tmplBox.String("footer.html") footer, _ := TmplBox.String("footer.html")
render, err := tmplBox.String("index.html") render, err := TmplBox.String("index.html")
if err != nil { if err != nil {
log.Send(3, err) utils.Log(3, err)
} }
t := template.New("message") t := template.New("message")
t.Funcs(template.FuncMap{ t.Funcs(template.FuncMap{
@ -25,18 +25,18 @@ func ExportIndexHTML() string {
return template.HTML(html) return template.HTML(html)
}, },
"VERSION": func() string { "VERSION": func() string {
return VERSION return "version here"
}, },
"underscore": func(html string) string { "underscore": func(html string) string {
return UnderScoreString(html) return utils.UnderScoreString(html)
}, },
}) })
t, _ = t.Parse(nav) t, _ = t.Parse(nav)
t, _ = t.Parse(footer) t, _ = t.Parse(footer)
t.Parse(render) t.Parse(render)
var tpl bytes.Buffer var tpl bytes.Buffer
if err := t.Execute(&tpl, out); err != nil { if err := t.Execute(&tpl, nil); err != nil {
log.Send(3, err) utils.Log(3, err)
} }
result := tpl.String() result := tpl.String()
return result return result

View File

@ -1,21 +1,13 @@
package main package core
import ( import (
"fmt" "fmt"
"github.com/ararog/timeago" "github.com/ararog/timeago"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/utils"
"strings" "strings"
"time" "time"
) )
type Failure struct {
Id int `db:"id,omitempty"`
Issue string `db:"issue"`
Method string `db:"method"`
Service int64 `db:"service"`
CreatedAt time.Time `db:"created_at"`
}
func (s *Service) CreateFailure(data FailureData) (int64, error) { func (s *Service) CreateFailure(data FailureData) (int64, error) {
fail := &Failure{ fail := &Failure{
Issue: data.Issue, Issue: data.Issue,
@ -23,7 +15,7 @@ func (s *Service) CreateFailure(data FailureData) (int64, error) {
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
s.Failures = append(s.Failures, fail) s.Failures = append(s.Failures, fail)
col := dbSession.Collection("failures") col := DbSession.Collection("failures")
uuid, err := col.Insert(fail) uuid, err := col.Insert(fail)
if uuid == nil { if uuid == nil {
return 0, err return 0, err
@ -33,14 +25,14 @@ func (s *Service) CreateFailure(data FailureData) (int64, error) {
func (s *Service) SelectAllFailures() []*Failure { func (s *Service) SelectAllFailures() []*Failure {
var fails []*Failure var fails []*Failure
col := dbSession.Collection("failures").Find("service", s.Id).OrderBy("-id") col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id")
col.All(&fails) col.All(&fails)
return fails return fails
} }
func (u *Service) DeleteFailures() { func (u *Service) DeleteFailures() {
var fails []*Failure var fails []*Failure
col := dbSession.Collection("failures") col := DbSession.Collection("failures")
col.Find("service", u.Id).All(&fails) col.Find("service", u.Id).All(&fails)
for _, fail := range fails { for _, fail := range fails {
fail.Delete() fail.Delete()
@ -49,7 +41,7 @@ func (u *Service) DeleteFailures() {
func (s *Service) LimitedFailures() []*Failure { func (s *Service) LimitedFailures() []*Failure {
var fails []*Failure var fails []*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 return fails
} }
@ -67,28 +59,28 @@ func (f *Failure) Ago() string {
} }
func (f *Failure) Delete() error { func (f *Failure) Delete() error {
col := dbSession.Collection("failures").Find("id", f.Id) col := DbSession.Collection("failures").Find("id", f.Id)
return col.Delete() return col.Delete()
} }
func CountFailures() uint64 { func CountFailures() uint64 {
col := dbSession.Collection("failures").Find() col := DbSession.Collection("failures").Find()
amount, err := col.Count() amount, err := col.Count()
if err != nil { if err != nil {
log.Send(2, err) utils.Log(2, err)
return 0 return 0
} }
return amount return amount
} }
func (s *Service) TotalFailures() (uint64, error) { func (s *Service) TotalFailures() (uint64, error) {
col := dbSession.Collection("failures").Find("service", s.Id) col := DbSession.Collection("failures").Find("service", s.Id)
amount, err := col.Count() amount, err := col.Count()
return amount, err return amount, err
} }
func (s *Service) TotalFailures24Hours() (uint64, error) { func (s *Service) TotalFailures24Hours() (uint64, error) {
col := dbSession.Collection("failures").Find("service", s.Id) col := DbSession.Collection("failures").Find("service", s.Id)
amount, err := col.Count() amount, err := col.Count()
return amount, err return amount, err
} }
@ -110,5 +102,9 @@ func (f *Failure) ParseError() string {
if err { if err {
return fmt.Sprintf("Incorrect HTTP Status Code") return fmt.Sprintf("Incorrect HTTP Status Code")
} }
err = strings.Contains(f.Issue, "connection refused")
if err {
return fmt.Sprintf("Connection Failed")
}
return f.Issue return f.Issue
} }

View File

@ -1,20 +1,16 @@
package main package core
import ( import (
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"time" "time"
"upper.io/db.v3" "upper.io/db.v3"
) )
type Hit struct { type Hit types.Hit
Id int `db:"id,omitempty"`
Service int64 `db:"service"`
Latency float64 `db:"latency"`
CreatedAt time.Time `db:"created_at"`
}
func hitCol() db.Collection { func hitCol() db.Collection {
return dbSession.Collection("hits") return DbSession.Collection("hits")
} }
func (s *Service) CreateHit(d HitData) (int64, error) { func (s *Service) CreateHit(d HitData) (int64, error) {
@ -25,7 +21,7 @@ func (s *Service) CreateHit(d HitData) (int64, error) {
} }
uuid, err := hitCol().Insert(h) uuid, err := hitCol().Insert(h)
if uuid == nil { if uuid == nil {
log.Send(2, err) utils.Log(2, err)
return 0, err return 0, err
} }
return uuid.(int64), err return uuid.(int64), err
@ -68,6 +64,9 @@ func (s *Service) TotalHits() (uint64, error) {
func (s *Service) Sum() (float64, error) { func (s *Service) Sum() (float64, error) {
var amount float64 var amount float64
hits, err := s.Hits() hits, err := s.Hits()
if err != nil {
utils.Log(2, err)
}
for _, h := range hits { for _, h := range hits {
amount += h.Latency amount += h.Latency
} }

View File

@ -1,17 +1,16 @@
package main package core
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"strconv" "strconv"
"time" "time"
"upper.io/db.v3" "upper.io/db.v3"
) )
var ( type Failure types.Failure
services []*Service
)
type Service struct { type Service struct {
Id int64 `db:"id,omitempty" json:"id"` Id int64 `db:"id,omitempty" json:"id"`
@ -40,11 +39,11 @@ type Service struct {
} }
func serviceCol() db.Collection { func serviceCol() db.Collection {
return dbSession.Collection("services") return DbSession.Collection("services")
} }
func SelectService(id int64) *Service { func SelectService(id int64) *Service {
for _, s := range services { for _, s := range CoreApp.Services {
if s.Id == id { if s.Id == id {
return s return s
} }
@ -56,10 +55,14 @@ func SelectAllServices() ([]*Service, error) {
var srvcs []*Service var srvcs []*Service
col := serviceCol().Find() col := serviceCol().Find()
err := col.All(&srvcs) err := col.All(&srvcs)
if err != nil {
utils.Log(3, err)
}
for _, s := range srvcs { for _, s := range srvcs {
s.Checkins = s.SelectAllCheckins() s.Checkins = s.SelectAllCheckins()
s.Failures = s.SelectAllFailures() s.Failures = s.SelectAllFailures()
} }
CoreApp.Services = srvcs
return srvcs, err return srvcs, err
} }
@ -106,9 +109,9 @@ func (s *Service) GraphData() string {
increment := "minute" increment := "minute"
since := time.Now().Add(time.Hour*-12 + time.Minute*0 + time.Second*0) since := time.Now().Add(time.Hour*-12 + time.Minute*0 + time.Second*0)
sql := fmt.Sprintf("SELECT date_trunc('%v', created_at), AVG(latency)*1000 AS value FROM hits WHERE service=%v AND created_at > '%v' GROUP BY 1 ORDER BY date_trunc ASC;", increment, s.Id, since.Format(time.RFC3339)) sql := fmt.Sprintf("SELECT date_trunc('%v', created_at), AVG(latency)*1000 AS value FROM hits WHERE service=%v AND created_at > '%v' GROUP BY 1 ORDER BY date_trunc ASC;", increment, s.Id, since.Format(time.RFC3339))
dated, err := dbSession.Query(db.Raw(sql)) dated, err := DbSession.Query(db.Raw(sql))
if err != nil { if err != nil {
log.Send(2, err) utils.Log(2, err)
return "" return ""
} }
for dated.Next() { for dated.Next() {
@ -120,7 +123,7 @@ func (s *Service) GraphData() string {
} }
data, err := json.Marshal(d) data, err := json.Marshal(d)
if err != nil { if err != nil {
log.Send(2, err) utils.Log(2, err)
return "" return ""
} }
return string(data) return string(data)
@ -151,18 +154,22 @@ func (s *Service) AvgUptime() string {
func (u *Service) RemoveArray() []*Service { func (u *Service) RemoveArray() []*Service {
var srvcs []*Service var srvcs []*Service
for _, s := range services { for _, s := range CoreApp.Services {
if s.Id != u.Id { if s.Id != u.Id {
srvcs = append(srvcs, s) srvcs = append(srvcs, s)
} }
} }
services = srvcs CoreApp.Services = srvcs
return srvcs return srvcs
} }
func (u *Service) Delete() error { func (u *Service) Delete() error {
res := serviceCol().Find("id", u.Id) res := serviceCol().Find("id", u.Id)
err := res.Delete() err := res.Delete()
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", u.Name, err))
return err
}
u.RemoveArray() u.RemoveArray()
OnDeletedService(u) OnDeletedService(u)
return err return err
@ -176,10 +183,11 @@ func (u *Service) Create() (int64, error) {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
uuid, err := serviceCol().Insert(u) uuid, err := serviceCol().Insert(u)
if uuid == nil { if uuid == nil {
utils.Log(3, fmt.Sprintf("Failed to create service %v. %v", u.Name, err))
return 0, err return 0, err
} }
u.Id = uuid.(int64) u.Id = uuid.(int64)
services = append(services, u) CoreApp.Services = append(CoreApp.Services, u)
go u.CheckQueue() go u.CheckQueue()
OnNewService(u) OnNewService(u)
return uuid.(int64), err return uuid.(int64), err
@ -187,7 +195,7 @@ func (u *Service) Create() (int64, error) {
func CountOnline() int { func CountOnline() int {
amount := 0 amount := 0
for _, v := range services { for _, v := range CoreApp.Services {
if v.Online { if v.Online {
amount++ amount++
} }

86
core/setup.go Normal file
View File

@ -0,0 +1,86 @@
package core
import (
"fmt"
"github.com/hunterlong/statup/utils"
"os"
)
func InsertDefaultComms() {
emailer := &Communication{
Method: "email",
Removable: false,
Enabled: false,
}
Create(emailer)
}
func DeleteConfig() {
err := os.Remove("./config.yml")
if err != nil {
utils.Log(3, err)
}
}
type ErrorResponse struct {
Error string
}
func LoadSampleData() error {
fmt.Println("Inserting Sample Data...")
s1 := &Service{
Name: "Google",
Domain: "https://google.com",
ExpectedStatus: 200,
Interval: 10,
Port: 0,
Type: "https",
Method: "GET",
}
s2 := &Service{
Name: "Statup.io",
Domain: "https://statup.io",
ExpectedStatus: 200,
Interval: 15,
Port: 0,
Type: "https",
Method: "GET",
}
s3 := &Service{
Name: "Statup.io SSL Check",
Domain: "https://statup.io",
ExpectedStatus: 200,
Interval: 15,
Port: 443,
Type: "tcp",
}
s4 := &Service{
Name: "Github Failing Check",
Domain: "https://github.com/thisisnotausernamemaybeitis",
ExpectedStatus: 200,
Interval: 15,
Port: 0,
Type: "https",
Method: "GET",
}
s1.Create()
s2.Create()
s3.Create()
s4.Create()
checkin := &Checkin{
Service: s2.Id,
Interval: 30,
Api: utils.NewSHA1Hash(18),
}
checkin.Create()
for i := 0; i < 20; i++ {
s1.Check()
s2.Check()
s3.Check()
s4.Check()
}
return nil
}

76
core/users.go Normal file
View File

@ -0,0 +1,76 @@
package core
import (
"fmt"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"golang.org/x/crypto/bcrypt"
"time"
)
type User types.User
func SelectUser(id int64) (*User, error) {
var user User
col := DbSession.Collection("users")
res := col.Find("id", id)
err := res.One(&user)
return &user, err
}
func SelectUsername(username string) (*User, error) {
var user User
col := DbSession.Collection("users")
res := col.Find("username", username)
err := res.One(&user)
return &user, err
}
func (u *User) Delete() error {
col := DbSession.Collection("users")
user := col.Find("id", u.Id)
return user.Delete()
}
func (u *User) Create() (int64, error) {
u.CreatedAt = time.Now()
u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10)
col := DbSession.Collection("users")
uuid, err := col.Insert(u)
if uuid == nil {
utils.Log(3, fmt.Sprintf("Failed to create user %v. %v", u.Username, err))
return 0, err
}
OnNewUser(u)
return uuid.(int64), err
}
func SelectAllUsers() ([]User, error) {
var users []User
col := DbSession.Collection("users").Find()
err := col.All(&users)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to load all users. %v", err))
}
return users, err
}
func AuthUser(username, password string) (*User, bool) {
var auth bool
user, err := SelectUsername(username)
if err != nil {
utils.Log(2, err)
return nil, false
}
if CheckHash(password, user.Password) {
auth = true
}
return user, auth
}
func CheckHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

View File

@ -1,89 +0,0 @@
package main
import (
"fmt"
"github.com/hunterlong/statup/log"
"time"
"upper.io/db.v3"
"upper.io/db.v3/lib/sqlbuilder"
"upper.io/db.v3/mysql"
"upper.io/db.v3/postgresql"
"upper.io/db.v3/sqlite"
)
var (
dbServer string
sqliteSettings sqlite.ConnectionURL
postgresSettings postgresql.ConnectionURL
mysqlSettings mysql.ConnectionURL
dbSession sqlbuilder.Database
)
func DbConnection(dbType string) error {
var err error
if dbType == "sqlite" {
sqliteSettings = sqlite.ConnectionURL{
Database: "statup.db",
}
dbSession, err = sqlite.Open(sqliteSettings)
if err != nil {
log.Send(3, err)
return err
}
} else if dbType == "mysql" {
if configs.Port == "" {
configs.Port = "3306"
}
mysqlSettings = mysql.ConnectionURL{
Database: configs.Database,
Host: configs.Host,
User: configs.User,
Password: configs.Password,
}
dbSession, err = mysql.Open(mysqlSettings)
if err != nil {
log.Send(3, err)
return err
}
} else {
if configs.Port == "" {
configs.Port = "5432"
}
host := fmt.Sprintf("%v:%v", configs.Host, configs.Port)
postgresSettings = postgresql.ConnectionURL{
Database: configs.Database,
Host: host,
User: configs.User,
Password: configs.Password,
}
dbSession, err = postgresql.Open(postgresSettings)
if err != nil {
log.Send(3, err)
return err
}
}
//dbSession.SetLogging(true)
dbServer = dbType
OnLoad(dbSession)
return err
}
func DatabaseMaintence() {
defer DatabaseMaintence()
since := time.Now().AddDate(0, 0, -7)
DeleteAllSince("failures", since)
DeleteAllSince("hits", since)
time.Sleep(60 * time.Minute)
}
func DeleteAllSince(table string, date time.Time) {
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
_, err := dbSession.Exec(db.Raw(sql))
if err != nil {
log.Send(2, err)
}
}
func Backup() {
}

View File

@ -1 +0,0 @@
statup.io

View File

@ -1,116 +0,0 @@
HTML,BODY {
background-color: #efefef;
}
.container {
padding-top: 20px;
padding-bottom: 20px;
max-width: 860px;
}
.navbar {
margin-bottom: 30px;
}
.lg_number {
font-size: 26pt;
font-weight: bold;
display: block;
color: #3e3e3e;
}
.text_perfect {
color: #33b418;
text-shadow: 0px 1px 0 #0e6702;
}
.text_good {
color: #33b418;
text-shadow: 0px 1px 0 #0e6702;
}
.text_ok {
color: #33b418;
text-shadow: 0px 1px 0 #0e6702;
}
.text_bad {
color: #33b418;
text-shadow: 0px 1px 0 #0e6702;
}
.stats_area {
text-align: center;
color: #a5a5a5;
}
.offline_bg {
background-color: white !important;
}
.online_list {
}
.footer {
text-decoration: none;
margin-top: 20px;
}
.footer A {
color: #cccccc;
}
.online_badge {
color: #fff;
background-color: #35b317;
}
.offline_badge {
color: #fff;
background-color: #c51919;
}
.progress {
margin-top: -20px;
margin-left: -20px;
margin-bottom: 15px;
width: calc(100% + 40px);
height: 3px;
border-radius: 0;
}
.card-body {
overflow: hidden;
}
.card-body H4 A {
color: #239e07;
text-decoration: none;
}
@media (max-width: 767px) {
.container {
margin-top: 0 !important;
padding: 0 !important;
}
.navbar {
margin-left: 0px;
margin-top: 0px;
width: 100%;
margin-bottom: 0;
}
.card-body {
font-size: 6pt;
}
.lg_number {
font-size: 1.2rem;
}
}

View File

@ -1 +0,0 @@
ok

View File

@ -1,23 +0,0 @@
#!/usr/bin/env bash
VERSION=v0.22
OS=osx
ARCH=x64
if [ `getconf LONG_BIT` = "64" ]
then
ARCH=x64
else
ARCH=x32
fi
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) OS=linux;;
Darwin*) OS=osx;;
CYGWIN*) OS=windows;;
MINGW*) OS=windows;;
*) OS="UNKNOWN:${unameOut}"
esac
FILE="https://github.com/hunterlong/statup/releases/download/$VERSION/statup-$OS-$ARCH"
curl -sS $FILE -o statup
chmod +x statup
mv statup /usr/local/bin/
statup version

View File

@ -4,8 +4,9 @@ import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/hunterlong/statup/log" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"gopkg.in/gomail.v2" "gopkg.in/gomail.v2"
"html/template" "html/template"
"time" "time"
@ -15,14 +16,16 @@ var (
emailQue *Que emailQue *Que
) )
type Email types.Email
type Que struct { type Que struct {
Mailer *gomail.Dialer Mailer *gomail.Dialer
Outgoing []*types.Email Outgoing []*Email
LastSent int LastSent int
LastSentTime time.Time LastSentTime time.Time
} }
func AddEmail(email *types.Email) { func AddEmail(email *Email) {
if emailQue == nil { if emailQue == nil {
return return
} }
@ -33,12 +36,12 @@ func EmailerQueue() {
if emailQue == nil { if emailQue == nil {
return return
} }
uniques := []*types.Email{} uniques := []*Email{}
for _, out := range emailQue.Outgoing { for _, out := range emailQue.Outgoing {
if isUnique(uniques, out) { if isUnique(uniques, out) {
msg := fmt.Sprintf("sending email to: %v \n", out.To) msg := fmt.Sprintf("sending email to: %v \n", out.To)
Send(out) Send(out)
log.Send(0, msg) utils.Log(0, msg)
uniques = append(uniques, out) uniques = append(uniques, out)
} }
} }
@ -48,7 +51,7 @@ func EmailerQueue() {
EmailerQueue() EmailerQueue()
} }
func isUnique(arr []*types.Email, obj *types.Email) bool { func isUnique(arr []*Email, obj *Email) bool {
for _, v := range arr { for _, v := range arr {
if v.To == obj.To && v.Subject == obj.Subject { if v.To == obj.To && v.Subject == obj.Subject {
return false return false
@ -57,7 +60,7 @@ func isUnique(arr []*types.Email, obj *types.Email) bool {
return true return true
} }
func Send(em *types.Email) { func Send(em *Email) {
source := EmailTemplate(em.Template, em.Data) source := EmailTemplate(em.Template, em.Data)
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", "info@betatude.com") m.SetHeader("From", "info@betatude.com")
@ -65,14 +68,14 @@ func Send(em *types.Email) {
m.SetHeader("Subject", em.Subject) m.SetHeader("Subject", em.Subject)
m.SetBody("text/html", source) m.SetBody("text/html", source)
if err := emailQue.Mailer.DialAndSend(m); err != nil { if err := emailQue.Mailer.DialAndSend(m); err != nil {
log.Send(2, err) utils.Log(2, err)
} }
emailQue.LastSent++ emailQue.LastSent++
emailQue.LastSentTime = time.Now() emailQue.LastSentTime = time.Now()
} }
func SendFailureEmail(service *Service) { func SendFailureEmail(service *core.Service) {
email := &types.Email{ email := &Email{
To: "info@socialeck.com", To: "info@socialeck.com",
Subject: fmt.Sprintf("Service %v is Failing", service.Name), Subject: fmt.Sprintf("Service %v is Failing", service.Name),
Template: "failure.html", Template: "failure.html",
@ -81,30 +84,30 @@ func SendFailureEmail(service *Service) {
AddEmail(email) AddEmail(email)
} }
func LoadMailer(config *types.Communication) *gomail.Dialer { func LoadMailer(config *core.Communication) *gomail.Dialer {
if config.Host == "" || config.Username == "" || config.Password == "" { if config.Host == "" || config.Username == "" || config.Password == "" {
return nil return nil
} }
emailQue = new(Que) emailQue = new(Que)
emailQue.Outgoing = []*types.Email{} emailQue.Outgoing = []*Email{}
emailQue.Mailer = gomail.NewDialer(config.Host, config.Port, config.Username, config.Password) emailQue.Mailer = gomail.NewDialer(config.Host, config.Port, config.Username, config.Password)
emailQue.Mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true} emailQue.Mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
return emailQue.Mailer return emailQue.Mailer
} }
func EmailTemplate(tmpl string, data interface{}) string { func EmailTemplate(tmpl string, data interface{}) string {
emailTpl, err := emailBox.String(tmpl) emailTpl, err := core.EmailBox.String(tmpl)
if err != nil { if err != nil {
log.Send(3, err) utils.Log(3, err)
} }
t := template.New("email") t := template.New("email")
t, err = t.Parse(emailTpl) t, err = t.Parse(emailTpl)
if err != nil { if err != nil {
log.Send(3, err) utils.Log(3, err)
} }
var tpl bytes.Buffer var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil { if err := t.Execute(&tpl, data); err != nil {
log.Send(2, err) utils.Log(2, err)
} }
result := tpl.String() result := tpl.String()
return result return result

View File

@ -1,21 +1,20 @@
package main package handlers
import ( import (
"crypto/sha1"
"encoding/json" "encoding/json"
"fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"math/rand" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/utils"
"net/http" "net/http"
) )
func ApiIndexHandler(w http.ResponseWriter, r *http.Request) { func ApiIndexHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(core) json.NewEncoder(w).Encode(core.CoreApp)
} }
func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) { func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
checkin := FindCheckin(vars["api"]) checkin := core.FindCheckin(vars["api"])
checkin.Receivehit() checkin.Receivehit()
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(checkin) json.NewEncoder(w).Encode(checkin)
@ -23,54 +22,31 @@ func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) {
func ApiServiceHandler(w http.ResponseWriter, r *http.Request) { func ApiServiceHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"])) service := core.SelectService(utils.StringInt(vars["id"]))
json.NewEncoder(w).Encode(service) json.NewEncoder(w).Encode(service)
} }
func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) { func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"])) service := core.SelectService(utils.StringInt(vars["id"]))
var s Service var s core.Service
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
decoder.Decode(&s) decoder.Decode(&s)
json.NewEncoder(w).Encode(service) json.NewEncoder(w).Encode(service)
} }
func ApiAllServicesHandler(w http.ResponseWriter, r *http.Request) { func ApiAllServicesHandler(w http.ResponseWriter, r *http.Request) {
services, _ := SelectAllServices() services, _ := core.SelectAllServices()
json.NewEncoder(w).Encode(services) json.NewEncoder(w).Encode(services)
} }
func ApiUserHandler(w http.ResponseWriter, r *http.Request) { func ApiUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
user, _ := SelectUser(StringInt(vars["id"])) user, _ := core.SelectUser(utils.StringInt(vars["id"]))
json.NewEncoder(w).Encode(user) json.NewEncoder(w).Encode(user)
} }
func ApiAllUsersHandler(w http.ResponseWriter, r *http.Request) { func ApiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
users, _ := SelectAllUsers() users, _ := core.SelectAllUsers()
json.NewEncoder(w).Encode(users) json.NewEncoder(w).Encode(users)
} }
func NewSHA1Hash(n ...int) string {
noRandomCharacters := 32
if len(n) > 0 {
noRandomCharacters = n[0]
}
randString := RandomString(noRandomCharacters)
hash := sha1.New()
hash.Write([]byte(randString))
bs := hash.Sum(nil)
return fmt.Sprintf("%x", bs)
}
var characterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
// RandomString generates a random string of n length
func RandomString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = characterRunes[rand.Intn(len(characterRunes))]
}
return string(b)
}

59
handlers/dashboard.go Normal file
View File

@ -0,0 +1,59 @@
package handlers
import (
"github.com/hunterlong/statup/core"
"net/http"
)
type dashboard struct {
Services []*core.Service
Core *core.Core
CountOnline int
CountServices int
Count24Failures uint64
}
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
err := core.ErrorResponse{}
ExecuteResponse(w, r, "login.html", err)
} else {
fails := core.CountFailures()
out := dashboard{core.CoreApp.Services, core.CoreApp, core.CountOnline(), len(core.CoreApp.Services), fails}
ExecuteResponse(w, r, "dashboard.html", out)
}
}
func LoginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := Store.Get(r, COOKIE_KEY)
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
user, auth := core.AuthUser(username, password)
if auth {
session.Values["authenticated"] = true
session.Values["user_id"] = user.Id
session.Save(r, w)
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."}
ExecuteResponse(w, r, "login.html", err)
}
}
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := Store.Get(r, COOKIE_KEY)
session.Values["authenticated"] = false
session.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func HelpHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "help.html", nil)
}

101
handlers/handlers.go Normal file
View File

@ -0,0 +1,101 @@
package handlers
import (
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"html/template"
"net/http"
"os"
"time"
)
const (
COOKIE_KEY = "statup_auth"
)
var (
Store *sessions.CookieStore
)
func RunHTTPServer() {
utils.Log(1, "Statup HTTP Server running on http://localhost:8080")
r := Router()
//for _, p := range allPlugins {
// info := p.GetInfo()
// for _, route := range p.Routes() {
// path := fmt.Sprintf("/plugins/%v/%v", info.Name, route.URL)
// r.Handle(path, http.HandlerFunc(route.Handler)).Methods(route.Method)
// fmt.Printf("Added Route %v for plugin %v\n", path, info.Name)
// }
//}
srv := &http.Server{
Addr: "0.0.0.0:8080",
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r,
}
err := srv.ListenAndServe()
if err != nil {
utils.Log(4, err)
}
}
func IsAuthenticated(r *http.Request) bool {
if os.Getenv("GO_ENV") == "test" {
return true
}
if core.CoreApp == nil {
return false
}
if Store == nil {
return false
}
session, err := Store.Get(r, COOKIE_KEY)
if err != nil {
return false
}
if session.Values["authenticated"] == nil {
return false
}
return session.Values["authenticated"].(bool)
}
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
utils.Http(r)
nav, _ := core.TmplBox.String("nav.html")
footer, _ := core.TmplBox.String("footer.html")
render, err := core.TmplBox.String(file)
if err != nil {
utils.Log(4, err)
}
t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
"safe": func(html string) template.HTML {
return template.HTML(html)
},
"Auth": func() bool {
return IsAuthenticated(r)
},
"VERSION": func() string {
return "Version here"
},
"underscore": func(html string) string {
return utils.UnderScoreString(html)
},
"User": func() *types.User {
return SessionUser(r)
},
})
t, _ = t.Parse(nav)
t, _ = t.Parse(footer)
t.Parse(render)
t.Execute(w, data)
}
type DbConfig types.DbConfig

23
handlers/index.go Normal file
View File

@ -0,0 +1,23 @@
package handlers
import (
"fmt"
"github.com/hunterlong/statup/core"
"net/http"
)
type index struct {
Core core.Core
Services []*core.Service
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
if core.CoreApp == nil {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
return
}
out := index{*core.CoreApp, core.CoreApp.Services}
first, _ := out.Services[0].LimitedHits()
fmt.Println(out.Services[0].Name, "start:", first[0].Id, "last:", first[len(first)-1].Id)
ExecuteResponse(w, r, "index.html", out)
}

17
handlers/misc.go Normal file
View File

@ -0,0 +1,17 @@
package handlers
import (
"github.com/hunterlong/statup/core"
"net/http"
)
func RobotsTxtHandler(w http.ResponseWriter, r *http.Request) {
robots := []byte(`User-agent: *
Disallow: /login
Disallow: /dashboard
Host: ` + core.CoreApp.Domain)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte(robots))
}

43
handlers/plugins.go Normal file
View File

@ -0,0 +1,43 @@
package handlers
import (
"github.com/hunterlong/statup/core"
"net/http"
"strings"
)
type PluginSelect struct {
Plugin string
Form string
Params map[string]interface{}
}
func PluginSavedHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
//vars := mux.Vars(r)
//plug := SelectPlugin(vars["name"])
data := make(map[string]string)
for k, v := range r.PostForm {
data[k] = strings.Join(v, "")
}
//plug.OnSave(structs.Map(data))
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func PluginsDownloadHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//vars := mux.Vars(r)
//name := vars["name"]
//DownloadPlugin(name)
core.LoadConfig()
http.Redirect(w, r, "/plugins", http.StatusSeeOther)
}

32
handlers/prometheus.go Normal file
View File

@ -0,0 +1,32 @@
package handlers
import (
"fmt"
"github.com/hunterlong/statup/core"
"net/http"
"strings"
)
func PrometheusHandler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Prometheus /metrics Request From IP: %v\n", r.RemoteAddr)
metrics := []string{}
system := fmt.Sprintf("statup_total_failures %v\n", core.CountFailures())
system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services))
metrics = append(metrics, system)
for _, v := range core.CoreApp.Services {
online := 1
if !v.Online {
online = 0
}
met := fmt.Sprintf("statup_service_failures{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, len(v.Failures))
met += fmt.Sprintf("statup_service_latency{id=\"%v\" name=\"%v\"} %0.0f\n", v.Id, v.Name, (v.Latency * 100))
met += fmt.Sprintf("statup_service_online{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, online)
met += fmt.Sprintf("statup_service_status_code{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, v.LastStatusCode)
met += fmt.Sprintf("statup_service_response_length{id=\"%v\" name=\"%v\"} %v", v.Id, v.Name, len([]byte(v.LastResponse)))
metrics = append(metrics, met)
}
output := strings.Join(metrics, "\n")
w.WriteHeader(http.StatusOK)
w.Write([]byte(output))
}

58
handlers/routes.go Normal file
View File

@ -0,0 +1,58 @@
package handlers
import (
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/core"
"net/http"
)
func Router() *mux.Router {
r := mux.NewRouter()
r.Handle("/", http.HandlerFunc(IndexHandler))
if core.UsingAssets {
cssHandler := http.FileServer(http.Dir("./assets/css"))
jsHandler := http.FileServer(http.Dir("./assets/js"))
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", cssHandler))
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", jsHandler))
} else {
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(core.CssBox.HTTPBox())))
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(core.JsBox.HTTPBox())))
}
r.Handle("/robots.txt", http.HandlerFunc(RobotsTxtHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(LoginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(LogoutHandler))
r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET")
r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)).Methods("GET")
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler))
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
r.Handle("/service/{id}/checkin", http.HandlerFunc(CheckinCreateUpdateHandler)).Methods("POST")
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
r.Handle("/users/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST")
r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST")
r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET")
r.Handle("/settings/email", http.HandlerFunc(SaveEmailSettingsHandler)).Methods("POST")
r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler))
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
r.Handle("/help", http.HandlerFunc(HelpHandler))
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceUpdateHandler)).Methods("POST")
r.Handle("/api/users", http.HandlerFunc(ApiAllUsersHandler))
r.Handle("/api/users/{id}", http.HandlerFunc(ApiUserHandler))
r.Handle("/metrics", http.HandlerFunc(PrometheusHandler)).Methods("GET")
Store = sessions.NewCookieStore([]byte("secretinfo"))
return r
}

153
handlers/services.go Normal file
View File

@ -0,0 +1,153 @@
package handlers
import (
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/utils"
"net/http"
"strconv"
)
func ServicesHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "services.html", core.CoreApp.Services)
}
func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
fmt.Println("service adding")
r.ParseForm()
name := r.PostForm.Get("name")
domain := r.PostForm.Get("domain")
method := r.PostForm.Get("method")
expected := r.PostForm.Get("expected")
status, _ := strconv.Atoi(r.PostForm.Get("expected_status"))
interval, _ := strconv.Atoi(r.PostForm.Get("interval"))
port, _ := strconv.Atoi(r.PostForm.Get("port"))
checkType := r.PostForm.Get("check_type")
service := &core.Service{
Name: name,
Domain: domain,
Method: method,
Expected: expected,
ExpectedStatus: status,
Interval: interval,
Type: checkType,
Port: port,
}
_, err := service.Create()
if err != nil {
go service.CheckQueue()
}
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
service.Delete()
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
ExecuteResponse(w, r, "service.html", service)
}
func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
var badge []byte
if service.Online {
badge = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="104" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h50v20H54z"/><path fill="url(#b)" d="M0 0h104v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">` + service.Name + `</text><text x="28" y="14">` + service.Name + `</text><text x="78" y="15" fill="#010101" fill-opacity=".3">online</text><text x="78" y="14">online</text></g></svg>`)
} else {
badge = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="99" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#e05d44" d="M54 0h45v20H54z"/><path fill="url(#b)" d="M0 0h99v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">` + service.Name + `</text><text x="28" y="14">` + service.Name + `</text><text x="75.5" y="15" fill="#010101" fill-opacity=".3">offline</text><text x="75.5" y="14">offline</text></g></svg>`)
}
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write(badge)
}
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
r.ParseForm()
name := r.PostForm.Get("name")
domain := r.PostForm.Get("domain")
method := r.PostForm.Get("method")
expected := r.PostForm.Get("expected")
status, _ := strconv.Atoi(r.PostForm.Get("expected_status"))
interval, _ := strconv.Atoi(r.PostForm.Get("interval"))
port, _ := strconv.Atoi(r.PostForm.Get("port"))
checkType := r.PostForm.Get("check_type")
service = &core.Service{
Name: name,
Domain: domain,
Method: method,
Expected: expected,
ExpectedStatus: status,
Interval: interval,
Type: checkType,
Port: port,
}
service.Update()
ExecuteResponse(w, r, "service.html", service)
}
func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
service.DeleteFailures()
core.CoreApp.Services, _ = core.SelectAllServices()
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
interval := utils.StringInt(r.PostForm.Get("interval"))
service := core.SelectService(utils.StringInt(vars["id"]))
checkin := &core.Checkin{
Service: service.Id,
Interval: interval,
Api: utils.NewSHA1Hash(18),
}
checkin.Create()
fmt.Println(checkin.Create())
ExecuteResponse(w, r, "service.html", service)
}

115
handlers/settings.go Normal file
View File

@ -0,0 +1,115 @@
package handlers
import (
"fmt"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/utils"
"net/http"
)
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//CoreApp.FetchPluginRepo()
//var pluginFields []PluginSelect
//
//for _, p := range allPlugins {
// fields := structs.Map(p.GetInfo())
//
// pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(), fields})
//}
//CoreApp.PluginFields = pluginFields
fmt.Println(core.CoreApp.Communications)
ExecuteResponse(w, r, "plugins.html", core.CoreApp)
}
func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
name := r.PostForm.Get("project")
if name != "" {
core.CoreApp.Name = name
}
description := r.PostForm.Get("description")
if description != core.CoreApp.Description {
core.CoreApp.Description = description
}
style := r.PostForm.Get("style")
if style != core.CoreApp.Style {
core.CoreApp.Style = style
}
footer := r.PostForm.Get("footer")
if footer != core.CoreApp.Footer {
core.CoreApp.Footer = footer
}
domain := r.PostForm.Get("domain")
if domain != core.CoreApp.Domain {
core.CoreApp.Domain = domain
}
core.CoreApp.Update()
core.OnSettingsSaved(core.CoreApp)
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
theme := r.PostForm.Get("theme")
variables := r.PostForm.Get("variables")
core.SaveAsset(theme, "scss/base.scss")
core.SaveAsset(variables, "scss/variables.scss")
core.CompileSASS()
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
core.CreateAllAssets()
core.UsingAssets = true
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
emailer := core.SelectCommunication(1)
r.ParseForm()
emailer.Host = r.PostForm.Get("host")
emailer.Username = r.PostForm.Get("username")
emailer.Password = r.PostForm.Get("password")
emailer.Port = int(utils.StringInt(r.PostForm.Get("port")))
emailer.Var1 = r.PostForm.Get("address")
core.Update(emailer)
//sample := &Email{
// To: SessionUser(r).Email,
// Subject: "Sample Email",
// Template: "error.html",
//}
//AddEmail(sample)
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}

122
handlers/setup.go Normal file
View File

@ -0,0 +1,122 @@
package handlers
import (
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"net/http"
"os"
"strconv"
"time"
)
func SetupHandler(w http.ResponseWriter, r *http.Request) {
if core.CoreApp != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
port := 5432
if os.Getenv("DB_CONN") == "mysql" {
port = 3306
}
var data interface{}
if os.Getenv("DB_CONN") != "" {
data = &types.DbConfig{
DbConn: os.Getenv("DB_CONN"),
DbHost: os.Getenv("DB_HOST"),
DbUser: os.Getenv("DB_USER"),
DbPass: os.Getenv("DB_PASS"),
DbData: os.Getenv("DB_DATABASE"),
DbPort: port,
Project: os.Getenv("NAME"),
Description: os.Getenv("DESCRIPTION"),
Email: "",
Username: "admin",
Password: "",
}
}
ExecuteResponse(w, r, "setup.html", data)
}
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
if core.CoreApp != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
dbHost := r.PostForm.Get("db_host")
dbUser := r.PostForm.Get("db_user")
dbPass := r.PostForm.Get("db_password")
dbDatabase := r.PostForm.Get("db_database")
dbConn := r.PostForm.Get("db_connection")
dbPort, _ := strconv.Atoi(r.PostForm.Get("db_port"))
project := r.PostForm.Get("project")
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
sample := r.PostForm.Get("sample_data")
description := r.PostForm.Get("description")
domain := r.PostForm.Get("domain")
email := r.PostForm.Get("email")
config := &core.DbConfig{
dbConn,
dbHost,
dbUser,
dbPass,
dbDatabase,
dbPort,
project,
description,
domain,
username,
password,
email,
nil,
}
err := config.Save()
if err != nil {
utils.Log(2, err)
config.Error = err
SetupResponseError(w, r, config)
return
}
core.Configs, err = core.LoadConfig()
if err != nil {
utils.Log(2, err)
config.Error = err
SetupResponseError(w, r, config)
return
}
err = core.DbConnection(core.Configs.Connection)
if err != nil {
utils.Log(2, err)
core.DeleteConfig()
config.Error = err
SetupResponseError(w, r, config)
return
}
admin := &core.User{
Username: config.Username,
Password: config.Password,
Email: email,
Admin: true,
}
admin.Create()
core.InsertDefaultComms()
if sample == "on" {
go core.LoadSampleData()
}
http.Redirect(w, r, "/", http.StatusSeeOther)
time.Sleep(2 * time.Second)
//mainProcess()
}
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
ExecuteResponse(w, r, "setup.html", a)
}

76
handlers/users.go Normal file
View File

@ -0,0 +1,76 @@
package handlers
import (
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"net/http"
"strconv"
)
func SessionUser(r *http.Request) *types.User {
session, _ := Store.Get(r, COOKIE_KEY)
if session == nil {
return nil
}
uuid := session.Values["user_id"]
var user *types.User
col := core.DbSession.Collection("users")
res := col.Find("id", uuid)
res.One(&user)
return user
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
users, _ := core.SelectAllUsers()
ExecuteResponse(w, r, "users.html", users)
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
fmt.Println("creating user")
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
email := r.PostForm.Get("email")
user := &core.User{
Username: username,
Password: password,
Email: email,
}
_, err := user.Create()
if err != nil {
utils.Log(2, err)
}
http.Redirect(w, r, "/users", http.StatusSeeOther)
}
func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
user, _ := core.SelectUser(int64(id))
users, _ := core.SelectAllUsers()
if len(users) == 1 {
http.Redirect(w, r, "/users", http.StatusSeeOther)
return
}
user.Delete()
http.Redirect(w, r, "/users", http.StatusSeeOther)
}

View File

@ -1,57 +0,0 @@
package log
import (
"github.com/fatih/color"
lg "log"
"os"
//"github.com/mkideal/log/logger"
)
var (
logFile *os.File
logLevel int
)
func init() {
var err error
logFile, err = os.OpenFile("statup.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
lg.Fatalf("error opening file: %v", err)
}
lg.SetOutput(logFile)
logEnv := os.Getenv("LOG")
if logEnv == "fatal" {
logLevel = 3
} else if logEnv == "debug" {
logLevel = 2
} else if logEnv == "info" {
logLevel = 1
} else {
logLevel = 0
}
}
func Panic(err interface{}) {
lg.Printf("PANIC: %v\n", err)
panic(err)
}
func Send(level int, err interface{}) {
switch level {
case 3:
lg.Printf("ERROR: %v\n", err)
color.Red("ERROR: %v\n", err)
os.Exit(2)
case 2:
lg.Printf("WARNING: %v\n", err)
color.Yellow("WARNING: %v\n", err)
case 1:
lg.Printf("INFO: %v\n", err)
color.Blue("INFO: %v\n", err)
case 0:
lg.Printf("%v\n", err)
color.White("%v\n", err)
}
}

199
main.go
View File

@ -1,169 +1,92 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/GeertJohan/go.rice" "github.com/GeertJohan/go.rice"
"github.com/go-yaml/yaml" "github.com/hunterlong/statup/core"
"github.com/gorilla/sessions" "github.com/hunterlong/statup/handlers"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"golang.org/x/crypto/bcrypt"
"io"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
plg "plugin" plg "plugin"
"strconv"
"strings" "strings"
) )
var ( var (
configs *Config VERSION string
core *Core
store *sessions.CookieStore
VERSION string
sqlBox *rice.Box
cssBox *rice.Box
scssBox *rice.Box
jsBox *rice.Box
tmplBox *rice.Box
emailBox *rice.Box
setupMode bool
allPlugins []plugin.PluginActions
logFile *os.File
) )
const (
pluginsRepo = "https://raw.githubusercontent.com/hunterlong/statup/master/plugins.json"
)
type Config struct {
Connection string `yaml:"connection"`
Host string `yaml:"host"`
Database string `yaml:"database"`
User string `yaml:"user"`
Password string `yaml:"password"`
Port string `yaml:"port"`
Secret string `yaml:"secret"`
}
type PluginRepos struct {
Plugins []PluginJSON
}
type PluginJSON struct {
Name string `json:"name"`
Description string `json:"description"`
Repo string `json:"repo"`
Author string `json:"author"`
Namespace string `json:"namespace"`
}
func (c *Core) FetchPluginRepo() []PluginJSON {
resp, err := http.Get(pluginsRepo)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var pk []PluginJSON
json.Unmarshal(body, &pk)
c.Repos = pk
return pk
}
func DownloadFile(filepath string, url string) error {
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
func init() { func init() {
LoadDotEnvs() LoadDotEnvs()
} }
func LoadDotEnvs() {
err := godotenv.Load()
if err != nil {
fmt.Println("Error loading .env file")
}
}
func main() { func main() {
defer logFile.Close() var err error
if len(os.Args) >= 2 { if len(os.Args) >= 2 {
CatchCLI(os.Args) CatchCLI(os.Args)
os.Exit(0) os.Exit(0)
} }
utils.Log(1, fmt.Sprintf("Starting Statup v%v\n", VERSION))
var err error
fmt.Printf("Starting Statup v%v\n", VERSION)
RenderBoxes() RenderBoxes()
hasAssets() core.HasAssets()
configs, err = LoadConfig() core.Configs, err = core.LoadConfig()
if err != nil { if err != nil {
log.Send(1, "config.yml file not found - starting in setup mode") utils.Log(2, "config.yml file not found - starting in setup mode")
setupMode = true core.SetupMode = true
RunHTTPServer() handlers.RunHTTPServer()
} }
mainProcess() mainProcess()
} }
func StringInt(s string) int64 { func RenderBoxes() {
num, _ := strconv.Atoi(s) core.SqlBox = rice.MustFindBox("source/sql")
return int64(num) core.CssBox = rice.MustFindBox("source/css")
core.ScssBox = rice.MustFindBox("source/scss")
core.JsBox = rice.MustFindBox("source/js")
core.TmplBox = rice.MustFindBox("source/tmpl")
core.EmailBox = rice.MustFindBox("source/emails")
}
func LoadDotEnvs() {
err := godotenv.Load()
if err == nil {
utils.Log(1, "Environment file '.env' Loaded")
}
} }
func mainProcess() { func mainProcess() {
var err error var err error
err = DbConnection(configs.Connection) err = core.DbConnection(core.Configs.Connection)
if err != nil { if err != nil {
throw(err) utils.Log(3, err)
} }
RunDatabaseUpgrades() core.RunDatabaseUpgrades()
core, err = SelectCore() core.CoreApp, err = core.SelectCore()
if err != nil { if err != nil {
log.Send(1, "Core database was not found, Statup is not setup yet.") utils.Log(2, "Core database was not found, Statup is not setup yet.")
RunHTTPServer() handlers.RunHTTPServer()
} }
CheckServices() core.CheckServices()
core.Communications, _ = SelectAllCommunications() core.CoreApp.Communications, err = core.SelectAllCommunications()
LoadDefaultCommunications() if err != nil {
utils.Log(2, err)
}
core.LoadDefaultCommunications()
go DatabaseMaintence() go core.DatabaseMaintence()
if !setupMode { if !core.SetupMode {
LoadPlugins() LoadPlugins()
RunHTTPServer() handlers.RunHTTPServer()
} }
} }
func throw(err error) {
fmt.Println("ERROR: ", err)
os.Exit(1)
}
func ForEachPlugin() { func ForEachPlugin() {
if len(core.Plugins) > 0 { if len(core.CoreApp.Plugins) > 0 {
//for _, p := range core.Plugins { //for _, p := range core.Plugins {
// p.OnShutdown() // p.OnShutdown()
//} //}
@ -179,7 +102,7 @@ func LoadPlugins() {
files, err := ioutil.ReadDir("./plugins") files, err := ioutil.ReadDir("./plugins")
if err != nil { if err != nil {
log.Send(1, fmt.Sprintf("Plugins directory was not found. Error: %v\n", err)) utils.Log(2, fmt.Sprintf("Plugins directory was not found. Error: %v\n", err))
return return
} }
for _, f := range files { for _, f := range files {
@ -192,7 +115,7 @@ func LoadPlugins() {
} }
plug, err := plg.Open("plugins/" + f.Name()) plug, err := plg.Open("plugins/" + f.Name())
if err != nil { if err != nil {
log.Send(2, fmt.Sprintf("Plugin '%v' could not load correctly.\n", f.Name())) utils.Log(2, fmt.Sprintf("Plugin '%v' could not load correctly.\n", f.Name()))
continue continue
} }
symPlugin, err := plug.Lookup("Plugin") symPlugin, err := plug.Lookup("Plugin")
@ -200,42 +123,16 @@ func LoadPlugins() {
var plugActions plugin.PluginActions var plugActions plugin.PluginActions
plugActions, ok := symPlugin.(plugin.PluginActions) plugActions, ok := symPlugin.(plugin.PluginActions)
if !ok { if !ok {
log.Send(2, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v\n", f.Name(), "unexpected type from module symbol")) utils.Log(2, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v\n", f.Name(), "unexpected type from module symbol"))
continue continue
} }
allPlugins = append(allPlugins, plugActions) //allPlugins = append(allPlugins, plugActions)
core.Plugins = append(core.Plugins, plugActions.GetInfo()) core.CoreApp.Plugins = append(core.CoreApp.Plugins, plugActions.GetInfo())
} }
OnLoad(dbSession) core.OnLoad(core.DbSession)
fmt.Printf("Loaded %v Plugins\n", len(allPlugins))
//utils.Log(1, fmt.Sprintf("Loaded %v Plugins\n", len(allPlugins)))
ForEachPlugin() ForEachPlugin()
} }
func RenderBoxes() {
sqlBox = rice.MustFindBox("sql")
cssBox = rice.MustFindBox("html/css")
scssBox = rice.MustFindBox("html/scss")
jsBox = rice.MustFindBox("html/js")
tmplBox = rice.MustFindBox("html/tmpl")
emailBox = rice.MustFindBox("html/emails")
}
func LoadConfig() (*Config, error) {
var config Config
file, err := ioutil.ReadFile("config.yml")
if err != nil {
return nil, err
}
err = yaml.Unmarshal(file, &config)
configs = &config
return &config, err
}
func HashPassword(password string) string {
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes)
}

View File

@ -3,6 +3,8 @@ package main
import ( import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/rendon/testcli" "github.com/rendon/testcli"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http" "net/http"
@ -19,19 +21,19 @@ var (
) )
func init() { func init() {
route = Router() route = handlers.Router()
} }
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
RenderBoxes() RenderBoxes()
os.Remove("./statup.db") os.Remove("./statup.db")
Router() handlers.Router()
LoadDotEnvs() LoadDotEnvs()
} }
func TestMySQLMakeConfig(t *testing.T) { func TestMySQLMakeConfig(t *testing.T) {
config := &DbConfig{ config := &core.DbConfig{
"mysql", "mysql",
os.Getenv("DB_HOST"), os.Getenv("DB_HOST"),
os.Getenv("DB_USER"), os.Getenv("DB_USER"),
@ -49,30 +51,30 @@ func TestMySQLMakeConfig(t *testing.T) {
err := config.Save() err := config.Save()
assert.Nil(t, err) assert.Nil(t, err)
_, err = LoadConfig() _, err = core.LoadConfig()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "mysql", configs.Connection) assert.Equal(t, "mysql", core.Configs.Connection)
err = DbConnection(configs.Connection) err = core.DbConnection(core.Configs.Connection)
assert.Nil(t, err) assert.Nil(t, err)
InsertDefaultComms() core.InsertDefaultComms()
} }
func TestInsertMysqlSample(t *testing.T) { func TestInsertMysqlSample(t *testing.T) {
err := LoadSampleData() err := core.LoadSampleData()
assert.Nil(t, err) assert.Nil(t, err)
} }
func TestSelectCoreMYQL(t *testing.T) { func TestSelectCoreMYQL(t *testing.T) {
var err error var err error
core, err = SelectCore() core.CoreApp, err = core.SelectCore()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Testing MYSQL", core.Name) assert.Equal(t, "Testing MYSQL", core.CoreApp.Name)
assert.Equal(t, VERSION, core.Version) assert.Equal(t, VERSION, core.CoreApp.Version)
} }
func TestSqliteMakeConfig(t *testing.T) { func TestSqliteMakeConfig(t *testing.T) {
config := &DbConfig{ config := &core.DbConfig{
"sqlite", "sqlite",
os.Getenv("DB_HOST"), os.Getenv("DB_HOST"),
os.Getenv("DB_USER"), os.Getenv("DB_USER"),
@ -90,22 +92,22 @@ func TestSqliteMakeConfig(t *testing.T) {
err := config.Save() err := config.Save()
assert.Nil(t, err) assert.Nil(t, err)
_, err = LoadConfig() _, err = core.LoadConfig()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "sqlite", configs.Connection) assert.Equal(t, "sqlite", core.Configs.Connection)
err = DbConnection(configs.Connection) err = core.DbConnection(core.Configs.Connection)
assert.Nil(t, err) assert.Nil(t, err)
InsertDefaultComms() core.InsertDefaultComms()
} }
func TestInsertSqliteSample(t *testing.T) { func TestInsertSqliteSample(t *testing.T) {
err := LoadSampleData() err := core.LoadSampleData()
assert.Nil(t, err) assert.Nil(t, err)
} }
func TestPostgresMakeConfig(t *testing.T) { func TestPostgresMakeConfig(t *testing.T) {
config := &DbConfig{ config := &core.DbConfig{
"postgres", "postgres",
os.Getenv("DB_HOST"), os.Getenv("DB_HOST"),
os.Getenv("DB_USER"), os.Getenv("DB_USER"),
@ -123,38 +125,38 @@ func TestPostgresMakeConfig(t *testing.T) {
err := config.Save() err := config.Save()
assert.Nil(t, err) assert.Nil(t, err)
_, err = LoadConfig() _, err = core.LoadConfig()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "postgres", configs.Connection) assert.Equal(t, "postgres", core.Configs.Connection)
err = DbConnection(configs.Connection) err = core.DbConnection(core.Configs.Connection)
assert.Nil(t, err) assert.Nil(t, err)
InsertDefaultComms() core.InsertDefaultComms()
} }
func TestInsertPostgresSample(t *testing.T) { func TestInsertPostgresSample(t *testing.T) {
err := LoadSampleData() err := core.LoadSampleData()
assert.Nil(t, err) assert.Nil(t, err)
} }
func TestSelectCorePostgres(t *testing.T) { func TestSelectCorePostgres(t *testing.T) {
var err error var err error
core, err = SelectCore() core.CoreApp, err = core.SelectCore()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Testing POSTGRES", core.Name) assert.Equal(t, "Testing POSTGRES", core.CoreApp.Name)
assert.Equal(t, VERSION, core.Version) assert.Equal(t, VERSION, core.CoreApp.Version)
} }
func TestSelectCore(t *testing.T) { func TestSelectCore(t *testing.T) {
var err error var err error
core, err = SelectCore() core.CoreApp, err = core.SelectCore()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Testing POSTGRES", core.Name) assert.Equal(t, "Testing POSTGRES", core.CoreApp.Name)
assert.Equal(t, VERSION, core.Version) assert.Equal(t, VERSION, core.CoreApp.Version)
} }
func TestUser_Create(t *testing.T) { func TestUser_Create(t *testing.T) {
user := &User{ user := &core.User{
Username: "admin", Username: "admin",
Password: "admin", Password: "admin",
Email: "info@testuser.com", Email: "info@testuser.com",
@ -164,14 +166,22 @@ func TestUser_Create(t *testing.T) {
assert.NotZero(t, id) assert.NotZero(t, id)
} }
func TestSelectAllServices(t *testing.T) {
var err error
services, err := core.SelectAllServices()
assert.Nil(t, err)
assert.Equal(t, 4, len(services))
}
func TestOneService_Check(t *testing.T) { func TestOneService_Check(t *testing.T) {
service := SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
t.Log(service)
assert.Equal(t, "Google", service.Name) assert.Equal(t, "Google", service.Name)
} }
func TestService_Create(t *testing.T) { func TestService_Create(t *testing.T) {
service := &Service{ service := &core.Service{
Name: "test service", Name: "test service",
Domain: "https://google.com", Domain: "https://google.com",
ExpectedStatus: 200, ExpectedStatus: 200,
@ -186,7 +196,7 @@ func TestService_Create(t *testing.T) {
} }
func TestService_Check(t *testing.T) { func TestService_Check(t *testing.T) {
service := SelectService(2) service := core.SelectService(2)
assert.NotNil(t, service) assert.NotNil(t, service)
assert.Equal(t, "Statup.io", service.Name) assert.Equal(t, "Statup.io", service.Name)
out := service.Check() out := service.Check()
@ -194,28 +204,28 @@ func TestService_Check(t *testing.T) {
} }
func TestService_AvgTime(t *testing.T) { func TestService_AvgTime(t *testing.T) {
service := SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
avg := service.AvgUptime() avg := service.AvgUptime()
assert.Equal(t, "100", avg) assert.Equal(t, "100", avg)
} }
func TestService_Online24(t *testing.T) { func TestService_Online24(t *testing.T) {
service := SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
online := service.Online24() online := service.Online24()
assert.Equal(t, float32(100), online) assert.Equal(t, float32(100), online)
} }
func TestService_GraphData(t *testing.T) { func TestService_GraphData(t *testing.T) {
service := SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
data := service.GraphData() data := service.GraphData()
assert.NotEmpty(t, data) assert.NotEmpty(t, data)
} }
func TestBadService_Create(t *testing.T) { func TestBadService_Create(t *testing.T) {
service := &Service{ service := &core.Service{
Name: "bad service", Name: "bad service",
Domain: "https://9839f83h72gey2g29278hd2od2d.com", Domain: "https://9839f83h72gey2g29278hd2od2d.com",
ExpectedStatus: 200, ExpectedStatus: 200,
@ -230,13 +240,13 @@ func TestBadService_Create(t *testing.T) {
} }
func TestBadService_Check(t *testing.T) { func TestBadService_Check(t *testing.T) {
service := SelectService(4) service := core.SelectService(4)
assert.NotNil(t, service) assert.NotNil(t, service)
assert.Equal(t, "Github Failing Check", service.Name) assert.Equal(t, "Github Failing Check", service.Name)
} }
func TestService_Hits(t *testing.T) { func TestService_Hits(t *testing.T) {
service := SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
hits, err := service.Hits() hits, err := service.Hits()
assert.Nil(t, err) assert.Nil(t, err)
@ -244,7 +254,7 @@ func TestService_Hits(t *testing.T) {
} }
func TestService_LimitedHits(t *testing.T) { func TestService_LimitedHits(t *testing.T) {
service := SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
hits, err := service.LimitedHits() hits, err := service.LimitedHits()
assert.Nil(t, err) assert.Nil(t, err)
@ -273,7 +283,7 @@ func TestPrometheusHandler(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
route.ServeHTTP(rr, req) route.ServeHTTP(rr, req)
t.Log(rr.Body.String()) t.Log(rr.Body.String())
assert.True(t, strings.Contains(rr.Body.String(), "statup_total_services 14")) assert.True(t, strings.Contains(rr.Body.String(), "statup_total_services 6"))
} }
func TestLoginHandler(t *testing.T) { func TestLoginHandler(t *testing.T) {
@ -348,7 +358,7 @@ func TestExportCommand(t *testing.T) {
c := testcli.Command("statup", "export") c := testcli.Command("statup", "export")
c.Run() c.Run()
t.Log(c.Stdout()) t.Log(c.Stdout())
assert.True(t, c.StdoutContains("Exported Statup index page")) assert.True(t, c.StdoutContains("Exporting Static 'index.html' page"))
} }
func TestAssetsCommand(t *testing.T) { func TestAssetsCommand(t *testing.T) {

300
setup.go
View File

@ -1,300 +0,0 @@
package main
import (
"fmt"
"github.com/go-yaml/yaml"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types"
"net/http"
"os"
"strconv"
"strings"
"time"
"upper.io/db.v3"
)
type DbConfig struct {
DbConn string `yaml:"connection"`
DbHost string `yaml:"host"`
DbUser string `yaml:"user"`
DbPass string `yaml:"password"`
DbData string `yaml:"database"`
DbPort int `yaml:"port"`
Project string `yaml:"-"`
Description string `yaml:"-"`
Domain string `yaml:"-"`
Username string `yaml:"-"`
Password string `yaml:"-"`
Email string `yaml:"-"`
Error error `yaml:"-"`
}
func RunDatabaseUpgrades() {
fmt.Println("Upgrading Tables...")
upgrade, _ := sqlBox.String("upgrade.sql")
requests := strings.Split(upgrade, ";")
for _, request := range requests {
_, err := dbSession.Exec(db.Raw(request + ";"))
if err != nil {
log.Send(2, err)
}
}
fmt.Println("Database Upgraded")
}
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
if core != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
dbHost := r.PostForm.Get("db_host")
dbUser := r.PostForm.Get("db_user")
dbPass := r.PostForm.Get("db_password")
dbDatabase := r.PostForm.Get("db_database")
dbConn := r.PostForm.Get("db_connection")
dbPort, _ := strconv.Atoi(r.PostForm.Get("db_port"))
project := r.PostForm.Get("project")
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
sample := r.PostForm.Get("sample_data")
description := r.PostForm.Get("description")
domain := r.PostForm.Get("domain")
email := r.PostForm.Get("email")
config := &DbConfig{
dbConn,
dbHost,
dbUser,
dbPass,
dbDatabase,
dbPort,
project,
description,
domain,
username,
password,
email,
nil,
}
err := config.Save()
if err != nil {
log.Send(2, err)
config.Error = err
SetupResponseError(w, r, config)
return
}
configs, err = LoadConfig()
if err != nil {
log.Send(2, err)
config.Error = err
SetupResponseError(w, r, config)
return
}
err = DbConnection(configs.Connection)
if err != nil {
log.Send(2, err)
DeleteConfig()
config.Error = err
SetupResponseError(w, r, config)
return
}
admin := &User{
Username: config.Username,
Password: config.Password,
Email: email,
Admin: true,
}
admin.Create()
InsertDefaultComms()
if sample == "on" {
go LoadSampleData()
}
http.Redirect(w, r, "/", http.StatusSeeOther)
time.Sleep(2 * time.Second)
mainProcess()
}
func InsertDefaultComms() {
emailer := &types.Communication{
Method: "email",
Removable: false,
Enabled: false,
}
Create(emailer)
}
func DeleteConfig() {
err := os.Remove("./config.yml")
if err != nil {
log.Send(3, err)
}
}
type ErrorResponse struct {
Error string
}
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
ExecuteResponse(w, r, "setup.html", a)
}
func (c *DbConfig) Clean() *DbConfig {
if os.Getenv("DB_PORT") != "" {
if c.DbConn == "postgres" {
c.DbHost = c.DbHost + ":" + os.Getenv("DB_PORT")
}
}
return c
}
func (c *DbConfig) Save() error {
var err error
config, err := os.Create("config.yml")
if err != nil {
log.Send(2, err)
return err
}
data, err := yaml.Marshal(c)
if err != nil {
log.Send(2, err)
return err
}
config.WriteString(string(data))
config.Close()
configs, err = LoadConfig()
if err != nil {
log.Send(2, err)
return err
}
err = DbConnection(configs.Connection)
if err != nil {
log.Send(2, err)
return err
}
DropDatabase()
CreateDatabase()
newCore := Core{
c.Project,
c.Description,
"config.yml",
NewSHA1Hash(5),
NewSHA1Hash(10),
"",
"",
c.Domain,
VERSION,
[]plugin.Info{},
[]PluginJSON{},
[]PluginSelect{},
nil,
false,
}
col := dbSession.Collection("core")
_, err = col.Insert(newCore)
return err
}
func DropDatabase() {
fmt.Println("Dropping Tables...")
down, _ := sqlBox.String("down.sql")
requests := strings.Split(down, ";")
for _, request := range requests {
_, err := dbSession.Exec(request)
if err != nil {
log.Send(2, err)
}
}
}
func CreateDatabase() {
fmt.Println("Creating Tables...")
sql := "postgres_up.sql"
if dbServer == "mysql" {
sql = "mysql_up.sql"
} else if dbServer == "sqlite3" {
sql = "sqlite_up.sql"
}
up, _ := sqlBox.String(sql)
requests := strings.Split(up, ";")
for _, request := range requests {
_, err := dbSession.Exec(request)
if err != nil {
log.Send(2, err)
}
}
//secret := NewSHA1Hash()
//db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan()
fmt.Println("Database Created")
//SampleData()
}
func LoadSampleData() error {
fmt.Println("Inserting Sample Data...")
s1 := &Service{
Name: "Google",
Domain: "https://google.com",
ExpectedStatus: 200,
Interval: 10,
Port: 0,
Type: "https",
Method: "GET",
}
s2 := &Service{
Name: "Statup.io",
Domain: "https://statup.io",
ExpectedStatus: 200,
Interval: 15,
Port: 0,
Type: "https",
Method: "GET",
}
s3 := &Service{
Name: "Statup.io SSL Check",
Domain: "https://statup.io",
ExpectedStatus: 200,
Interval: 15,
Port: 443,
Type: "tcp",
}
s4 := &Service{
Name: "Github Failing Check",
Domain: "https://github.com/thisisnotausernamemaybeitis",
ExpectedStatus: 200,
Interval: 15,
Port: 0,
Type: "https",
Method: "GET",
}
s1.Create()
s2.Create()
s3.Create()
s4.Create()
checkin := &Checkin{
Service: s2.Id,
Interval: 30,
Api: NewSHA1Hash(18),
}
checkin.Create()
for i := 0; i < 20; i++ {
s1.Check()
s2.Check()
s3.Check()
s4.Check()
}
return nil
}

View File

@ -1,70 +1,86 @@
HTML, BODY { HTML, BODY {
background-color: #fcfcfc; } background-color: #fcfcfc;
}
.container { .container {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
max-width: 860px; } max-width: 860px;
}
.online_list .badge { .online_list .badge {
margin-top: 0.2rem; } margin-top: 0.2rem;
}
.navbar { .navbar {
margin-bottom: 30px; } margin-bottom: 30px;
}
.btn-sm { .btn-sm {
line-height: 1.3; line-height: 1.3;
font-size: 0.75rem; } font-size: 0.75rem;
}
.view_service_btn { .view_service_btn {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
right: 40px; } right: 40px;
}
.service_lower_info { .service_lower_info {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
left: 40px; left: 40px;
color: #d1ffca; color: #d1ffca;
font-size: 0.85rem; } font-size: 0.85rem;
}
.lg_number { .lg_number {
font-size: 26pt; font-size: 26pt;
font-weight: bold; font-weight: bold;
display: block; display: block;
color: #474747; } color: #474747;
}
.stats_area { .stats_area {
text-align: center; text-align: center;
color: #a5a5a5; } color: #a5a5a5;
}
.lower_canvas { .lower_canvas {
height: 55px; height: 55px;
width: 100%; width: 100%;
background-color: #48d338; background-color: #48d338;
padding: 17px 10px; } padding: 17px 10px;
}
.lower_canvas SPAN { .lower_canvas SPAN {
font-size: 1rem; } font-size: 1rem;
}
.footer { .footer {
text-decoration: none; text-decoration: none;
margin-top: 20px; } margin-top: 20px;
}
.footer A { .footer A {
color: #aaaaaa; color: #aaaaaa;
text-decoration: none; } text-decoration: none;
}
.footer A:HOVER { .footer A:HOVER {
color: #6d6d6d; } color: #6d6d6d;
}
.online_badge { .online_badge {
color: #fff; color: #fff;
background-color: #35b317; } background-color: #35b317;
}
.offline_badge { .offline_badge {
color: #fff; color: #fff;
background-color: #c51919; } background-color: #c51919;
}
.progress { .progress {
margin-top: -20px; margin-top: -20px;
@ -72,22 +88,27 @@ HTML, BODY {
margin-bottom: 15px; margin-bottom: 15px;
width: calc(100% + 40px); width: calc(100% + 40px);
height: 3px; height: 3px;
border-radius: 0; } border-radius: 0;
}
.card { .card {
background-color: #fff; } background-color: #fff;
}
.card-body { .card-body {
overflow: hidden; } overflow: hidden;
}
.card-body H4 A { .card-body H4 A {
color: #239e07; color: #239e07;
text-decoration: none; } text-decoration: none;
}
.chart-container { .chart-container {
position: relative; position: relative;
height: 170px; height: 170px;
width: 100%; } width: 100%;
}
.CodeMirror { .CodeMirror {
/* Bootstrap Settings */ /* Bootstrap Settings */
@ -107,81 +128,100 @@ HTML, BODY {
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
/* Code Mirror Settings */ /* Code Mirror Settings */
font-family: monospace; font-family: monospace;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
height: 60vh; } height: 60vh;
}
.CodeMirror-focused { .CodeMirror-focused {
/* Bootstrap Settings */ /* Bootstrap Settings */
border-color: #66afe9; border-color: #66afe9;
outline: 0; outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; } transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
@media (max-width: 767px) { @media (max-width: 767px) {
.sm-container { .sm-container {
margin-top: 40px !important; margin-top: 40px !important;
padding: 0 !important; } padding: 0 !important;
}
.list-group-item H5 { .list-group-item H5 {
font-size: 0.9rem; } font-size: 0.9rem;
}
.container { .container {
padding: 0 !important; } padding: 0 !important;
}
.navbar { .navbar {
margin-left: 0px; margin-left: 0px;
margin-top: 0px; margin-top: 0px;
width: 100%; width: 100%;
margin-bottom: 0; } margin-bottom: 0;
}
.card-body { .card-body {
font-size: 6pt; font-size: 6pt;
padding: 5px 5px; } padding: 5px 5px;
}
.lg_number { .lg_number {
font-size: 1.5rem; } font-size: 1.5rem;
}
.stats_area { .stats_area {
margin-top: 35px !important; margin-top: 35px !important;
margin-bottom: 35px !important; } margin-bottom: 35px !important;
}
.stats_area .col-4 { .stats_area .col-4 {
padding-left: 0; padding-left: 0;
padding-right: 0; } padding-right: 0;
}
.lower_canvas SPAN { .lower_canvas SPAN {
font-size: 0.9rem; font-size: 0.9rem;
float: left; } float: left;
}
.btn-sm { .btn-sm {
line-height: 0.9rem; line-height: 0.9rem;
font-size: 0.65rem; } font-size: 0.65rem;
}
.full-col-12 { .full-col-12 {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; } padding-right: 0px;
}
.card { .card {
border: 0; border: 0;
border-radius: 0; } border-radius: 0;
}
.list-group-item { .list-group-item {
border-top: 1px solid #e4e4e4; border-top: 1px solid #e4e4e4;
border: 0px; } border: 0px;
}
.list-group-item:first-child { .list-group-item:first-child {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; } border-top-right-radius: 0;
}
.list-group-item:last-child { .list-group-item:last-child {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-bottom-left-radius: 0; } border-bottom-left-radius: 0;
}
.list-group-item P { .list-group-item P {
font-size: 0.7rem; } } font-size: 0.7rem;
}
}
/*# sourceMappingURL=base.css.map */ /*# sourceMappingURL=base.css.map */

1
source/css/base.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../scss/base.scss","../scss/variables.scss"],"names":[],"mappings":"AAGA;EACI,kBCJe;;;ADOnB;EACI;EACA;EACA,WCTQ;;;ADYZ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI,WClCc;EDmCd;EACA;EACA,OCtCe;;;ADyCnB;EACI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI,kBC5Fc;;;AD+FlB;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;EACA;;;AASJ;AACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;;;AAGF;AACE;EACA;EACA;EACA;EACA;;;AAIF;EAEI;IACI;IACA;;;EAGJ;IACI;;;EAGJ;IACI;;;EAGJ;IACI;IACA;IACA;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI;IACA;;;EAGJ;IACI","file":"base.css"}

View File

@ -6,6 +6,13 @@ $(".service_li").on('click', function() {
}); });
$(".disable_click").on('click', function() {
$(this).prop("disabled", true);
$(this).text("Loading...");
return true;
});
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) {

View File

@ -1,10 +1,6 @@
var currentLocation = window.location; var currentLocation = window.location;
$("#domain_input").val(currentLocation.origin); $("#domain_input").val(currentLocation.origin);
function forceLower(strInput) {
strInput.value=strInput.value.toLowerCase();
}
$('select#database_type').on('change', function(){ $('select#database_type').on('change', function(){
var selected = $('#database_type option:selected').val(); var selected = $('#database_type option:selected').val();
if (selected=="sqlite") { if (selected=="sqlite") {

View File

@ -5,14 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
{{if .Core.OfflineAssets}} {{if .Core.OfflineAssets}}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <link rel="stylesheet" href="https://assets.statup.io/bootstrap.min.css">
<link rel="stylesheet" href="https://statup.io/base.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
{{ else }} {{ else }}
<link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css">
<script src="/js/Chart.bundle.min.js"></script>
{{end}} {{end}}
<link rel="stylesheet" href="/css/base.css">
<title>{{.Core.Name}} Status</title> <title>{{.Core.Name}} Status</title>
</head> </head>
<body> <body>
@ -96,6 +94,17 @@
{{template "footer"}} {{template "footer"}}
{{if .Core.OfflineAssets}}
<script src="https://assets.statup.io/jquery-3.3.1.slim.min.js"></script>
<script src="https://assets.statup.io/bootstrap.min.js"></script>
<script src="https://assets.statup.io/Chart.bundle.min.js"></script>
{{ else }}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/Chart.bundle.min.js"></script>
{{end}}
<script> <script>
{{ range .Services }} {{ range .Services }}
{{ if .AvgTime }} {{ if .AvgTime }}
@ -247,6 +256,7 @@ var chartdata = new Chart(ctx, {
{{ end }} {{ end }}
</script> </script>
<script src="/js/main.js"></script>
{{ if .Core.Style }} {{ if .Core.Style }}
<style> <style>
@ -255,14 +265,5 @@ var chartdata = new Chart(ctx, {
{{ end }} {{ end }}
{{if .Core.OfflineAssets}}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
{{ else }}
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/main.js"></script>
{{end}}
</body> </body>
</html> </html>

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
<script src="/js/Chart.bundle.min.js"></script>
<title>Statup | Setup</title> <title>Statup | Setup</title>
</head> </head>
@ -77,7 +76,7 @@
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Admin Username</label> <label for="formGroupExampleInput">Admin Username</label>
<input type="text" name="username" class="form-control" value="{{.Username}}" id="formGroupExampleInput" value="admin" placeholder="admin" onkeyup="return forceLower(this);" required> <input type="text" name="username" class="form-control" value="{{.Username}}" id="formGroupExampleInput" value="admin" placeholder="admin" required>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -1,6 +1,44 @@
package types package types
import "time" import (
"time"
)
type User struct {
Id int64 `db:"id,omitempty" json:"id"`
Username string `db:"username" json:"username"`
Password string `db:"password" json:"-"`
Email string `db:"email" json:"-"`
ApiKey string `db:"api_key" json:"api_key"`
ApiSecret string `db:"api_secret" json:"-"`
Admin bool `db:"administrator" json:"admin"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
type Hit struct {
Id int `db:"id,omitempty"`
Service int64 `db:"service"`
Latency float64 `db:"latency"`
CreatedAt time.Time `db:"created_at"`
}
type Failure struct {
Id int `db:"id,omitempty"`
Issue string `db:"issue"`
Method string `db:"method"`
Service int64 `db:"service"`
CreatedAt time.Time `db:"created_at"`
}
type Checkin struct {
Id int `db:"id,omitempty"`
Service int64 `db:"service"`
Interval int64 `db:"check_interval"`
Api string `db:"api"`
CreatedAt time.Time `db:"created_at"`
Hits int64 `json:"hits"`
Last time.Time `json:"last"`
}
type Communication struct { type Communication struct {
Id int64 `db:"id,omitempty" json:"id"` Id int64 `db:"id,omitempty" json:"id"`
@ -25,3 +63,45 @@ type Email struct {
Template string Template string
Data interface{} Data interface{}
} }
type Config struct {
Connection string `yaml:"connection"`
Host string `yaml:"host"`
Database string `yaml:"database"`
User string `yaml:"user"`
Password string `yaml:"password"`
Port string `yaml:"port"`
Secret string `yaml:"secret"`
}
type DbConfig struct {
DbConn string `yaml:"connection"`
DbHost string `yaml:"host"`
DbUser string `yaml:"user"`
DbPass string `yaml:"password"`
DbData string `yaml:"database"`
DbPort int `yaml:"port"`
Project string `yaml:"-"`
Description string `yaml:"-"`
Domain string `yaml:"-"`
Username string `yaml:"-"`
Password string `yaml:"-"`
Email string `yaml:"-"`
Error error `yaml:"-"`
}
type PluginRepos struct {
Plugins []PluginJSON
}
type PluginJSON struct {
Name string `json:"name"`
Description string `json:"description"`
Repo string `json:"repo"`
Author string `json:"author"`
Namespace string `json:"namespace"`
}
type FailureData struct {
Issue string
}

View File

@ -1,94 +0,0 @@
package main
import (
"github.com/hunterlong/statup/log"
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)
type User struct {
Id int64 `db:"id,omitempty" json:"id"`
Username string `db:"username" json:"username"`
Password string `db:"password" json:"-"`
Email string `db:"email" json:"-"`
ApiKey string `db:"api_key" json:"api_key"`
ApiSecret string `db:"api_secret" json:"-"`
Admin bool `db:"administrator" json:"admin"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func SessionUser(r *http.Request) *User {
session, _ := store.Get(r, cookieKey)
if session == nil {
return nil
}
uuid := session.Values["user_id"]
var user *User
col := dbSession.Collection("users")
res := col.Find("id", uuid)
res.One(&user)
return user
}
func SelectUser(id int64) (*User, error) {
var user User
col := dbSession.Collection("users")
res := col.Find("id", id)
err := res.One(&user)
return &user, err
}
func SelectUsername(username string) (*User, error) {
var user User
col := dbSession.Collection("users")
res := col.Find("username", username)
err := res.One(&user)
return &user, err
}
func (u *User) Delete() error {
col := dbSession.Collection("users")
user := col.Find("id", u.Id)
return user.Delete()
}
func (u *User) Create() (int64, error) {
u.CreatedAt = time.Now()
u.Password = HashPassword(u.Password)
u.ApiKey = NewSHA1Hash(5)
u.ApiSecret = NewSHA1Hash(10)
col := dbSession.Collection("users")
uuid, err := col.Insert(u)
if uuid == nil {
log.Send(2, err)
return 0, err
}
OnNewUser(u)
return uuid.(int64), err
}
func SelectAllUsers() ([]User, error) {
var users []User
col := dbSession.Collection("users").Find()
err := col.All(&users)
return users, err
}
func AuthUser(username, password string) (*User, bool) {
var auth bool
user, err := SelectUsername(username)
if err != nil {
log.Send(2, err)
return nil, false
}
if CheckHash(password, user.Password) {
auth = true
}
return user, auth
}
func CheckHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

36
utils/encryption.go Normal file
View File

@ -0,0 +1,36 @@
package utils
import (
"crypto/sha1"
"fmt"
"golang.org/x/crypto/bcrypt"
"math/rand"
)
func HashPassword(password string) string {
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes)
}
func NewSHA1Hash(n ...int) string {
noRandomCharacters := 32
if len(n) > 0 {
noRandomCharacters = n[0]
}
randString := RandomString(noRandomCharacters)
hash := sha1.New()
hash.Write([]byte(randString))
bs := hash.Sum(nil)
return fmt.Sprintf("%x", bs)
}
var characterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
// RandomString generates a random string of n length
func RandomString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = characterRunes[rand.Intn(len(characterRunes))]
}
return string(b)
}

97
utils/log.go Normal file
View File

@ -0,0 +1,97 @@
package utils
import (
"fmt"
"github.com/fatih/color"
"gopkg.in/natefinch/lumberjack.v2"
lg "log"
"net/http"
"os"
"os/signal"
"syscall"
)
var (
logFile *os.File
logLevel int
fmtLogs *lg.Logger
ljLogger *lumberjack.Logger
)
func init() {
var err error
logFile, err = os.OpenFile("./statup.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
lg.Fatalf("error opening file: %v", err)
}
ljLogger = &lumberjack.Logger{
Filename: "./statup.log",
MaxSize: 16,
MaxBackups: 3,
MaxAge: 28,
}
fmtLogs = lg.New(logFile, "", lg.Ldate|lg.Ltime)
fmtLogs.SetOutput(ljLogger)
logEnv := os.Getenv("LOG")
if logEnv == "fatal" {
logLevel = 3
} else if logEnv == "debug" {
logLevel = 2
} else if logEnv == "info" {
logLevel = 1
} else {
logLevel = 0
}
rotate()
}
func rotate() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for {
<-c
ljLogger.Rotate()
}
}()
}
func Panic(err interface{}) {
lg.Printf("PANIC: %v\n", err)
panic(err)
}
func Log(level int, err interface{}) {
switch level {
case 5:
lg.Fatalf("PANIC: %v\n", err)
case 4:
lg.Printf("FATAL: %v\n", err)
//color.Red("ERROR: %v\n", err)
//os.Exit(2)
case 3:
lg.Printf("ERROR: %v\n", err)
//color.Red("ERROR: %v\n", err)
case 2:
lg.Printf("WARNING: %v\n", err)
//color.Yellow("WARNING: %v\n", err)
case 1:
lg.Printf("INFO: %v\n", err)
//color.Blue("INFO: %v\n", err)
case 0:
lg.Printf("%v\n", err)
color.White("%v\n", err)
}
}
func Http(r *http.Request) {
msg := fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host)
lg.Printf("WEB: %v\n", msg)
}
func ReportLog() {
}

35
utils/utils.go Normal file
View File

@ -0,0 +1,35 @@
package utils
import (
"regexp"
"strconv"
"strings"
)
func StringInt(s string) int64 {
num, _ := strconv.Atoi(s)
return int64(num)
}
func UnderScoreString(str string) string {
// convert every letter to lower case
newStr := strings.ToLower(str)
// convert all spaces/tab to underscore
regExp := regexp.MustCompile("[[:space:][:blank:]]")
newStrByte := regExp.ReplaceAll([]byte(newStr), []byte("_"))
regExp = regexp.MustCompile("`[^a-z0-9]`i")
newStrByte = regExp.ReplaceAll(newStrByte, []byte("_"))
regExp = regexp.MustCompile("[!/']")
newStrByte = regExp.ReplaceAll(newStrByte, []byte("_"))
// and remove underscore from beginning and ending
newStr = strings.TrimPrefix(string(newStrByte), "_")
newStr = strings.TrimSuffix(newStr, "_")
return newStr
}

650
web.go
View File

@ -1,650 +0,0 @@
package main
import (
"fmt"
"github.com/fatih/structs"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/types"
"html/template"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
)
const (
cookieKey = "statup_auth"
)
func Router() *mux.Router {
r := mux.NewRouter()
r.Handle("/", http.HandlerFunc(IndexHandler))
if useAssets {
cssHandler := http.FileServer(http.Dir("./assets/css"))
jsHandler := http.FileServer(http.Dir("./assets/js"))
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", cssHandler))
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", jsHandler))
} else {
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox())))
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox())))
}
r.Handle("/robots.txt", http.HandlerFunc(RobotsTxtHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(LoginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(LogoutHandler))
r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET")
r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)).Methods("GET")
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler))
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
r.Handle("/service/{id}/checkin", http.HandlerFunc(CheckinCreateUpdateHandler)).Methods("POST")
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
r.Handle("/users/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST")
r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST")
r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET")
r.Handle("/settings/email", http.HandlerFunc(SaveEmailSettingsHandler)).Methods("POST")
r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler))
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
r.Handle("/help", http.HandlerFunc(HelpHandler))
r.Handle("/api", http.HandlerFunc(ApiIndexHandler))
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler))
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET")
r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceUpdateHandler)).Methods("POST")
r.Handle("/api/users", http.HandlerFunc(ApiAllUsersHandler))
r.Handle("/api/users/{id}", http.HandlerFunc(ApiUserHandler))
r.Handle("/metrics", http.HandlerFunc(PrometheusHandler)).Methods("GET")
store = sessions.NewCookieStore([]byte("secretinfo"))
return r
}
func RunHTTPServer() {
fmt.Println("Statup HTTP Server running on http://localhost:8080")
r := Router()
for _, p := range allPlugins {
info := p.GetInfo()
for _, route := range p.Routes() {
path := fmt.Sprintf("/plugins/%v/%v", info.Name, route.URL)
r.Handle(path, http.HandlerFunc(route.Handler)).Methods(route.Method)
fmt.Printf("Added Route %v for plugin %v\n", path, info.Name)
}
}
srv := &http.Server{
Addr: "0.0.0.0:8080",
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r,
}
srv.ListenAndServe()
}
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, cookieKey)
session.Values["authenticated"] = false
session.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func LoginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, cookieKey)
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
user, auth := AuthUser(username, password)
if auth {
session.Values["authenticated"] = true
session.Values["user_id"] = user.Id
session.Save(r, w)
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
err := ErrorResponse{Error: "Incorrect login information submitted, try again."}
ExecuteResponse(w, r, "login.html", err)
}
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
fmt.Println("creating user")
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
email := r.PostForm.Get("email")
user := &User{
Username: username,
Password: password,
Email: email,
}
_, err := user.Create()
if err != nil {
log.Send(2, err)
}
http.Redirect(w, r, "/users", http.StatusSeeOther)
}
func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
fmt.Println("service adding")
r.ParseForm()
name := r.PostForm.Get("name")
domain := r.PostForm.Get("domain")
method := r.PostForm.Get("method")
expected := r.PostForm.Get("expected")
status, _ := strconv.Atoi(r.PostForm.Get("expected_status"))
interval, _ := strconv.Atoi(r.PostForm.Get("interval"))
port, _ := strconv.Atoi(r.PostForm.Get("port"))
checkType := r.PostForm.Get("check_type")
service := &Service{
Name: name,
Domain: domain,
Method: method,
Expected: expected,
ExpectedStatus: status,
Interval: interval,
Type: checkType,
Port: port,
}
_, err := service.Create()
if err != nil {
go service.CheckQueue()
}
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func PrometheusHandler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Prometheus /metrics Request From IP: %v\n", r.RemoteAddr)
metrics := []string{}
system := fmt.Sprintf("statup_total_failures %v\n", CountFailures())
system += fmt.Sprintf("statup_total_services %v", len(services))
metrics = append(metrics, system)
for _, v := range services {
online := 1
if !v.Online {
online = 0
}
met := fmt.Sprintf("statup_service_failures{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, len(v.Failures))
met += fmt.Sprintf("statup_service_latency{id=\"%v\" name=\"%v\"} %0.0f\n", v.Id, v.Name, (v.Latency * 100))
met += fmt.Sprintf("statup_service_online{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, online)
met += fmt.Sprintf("statup_service_status_code{id=\"%v\" name=\"%v\"} %v\n", v.Id, v.Name, v.LastStatusCode)
met += fmt.Sprintf("statup_service_response_length{id=\"%v\" name=\"%v\"} %v", v.Id, v.Name, len([]byte(v.LastResponse)))
metrics = append(metrics, met)
}
output := strings.Join(metrics, "\n")
w.WriteHeader(http.StatusOK)
w.Write([]byte(output))
}
func RobotsTxtHandler(w http.ResponseWriter, r *http.Request) {
robots := []byte(`User-agent: *
Disallow: /login
Disallow: /dashboard
Host: ` + core.Domain)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte(robots))
}
func SetupHandler(w http.ResponseWriter, r *http.Request) {
if core != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
port := 5432
if os.Getenv("DB_CONN") == "mysql" {
port = 3306
}
var data interface{}
if os.Getenv("DB_CONN") != "" {
data = &DbConfig{
DbConn: os.Getenv("DB_CONN"),
DbHost: os.Getenv("DB_HOST"),
DbUser: os.Getenv("DB_USER"),
DbPass: os.Getenv("DB_PASS"),
DbData: os.Getenv("DB_DATABASE"),
DbPort: port,
Project: os.Getenv("NAME"),
Description: os.Getenv("DESCRIPTION"),
Email: "",
Username: "admin",
Password: "",
}
}
ExecuteResponse(w, r, "setup.html", data)
}
type index struct {
Core Core
Services []*Service
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
if core == nil {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
return
}
out := index{*core, services}
first, _ := out.Services[0].LimitedHits()
fmt.Println(out.Services[0].Name, "start:", first[0].Id, "last:", first[len(first)-1].Id)
ExecuteResponse(w, r, "index.html", out)
}
type dashboard struct {
Services []*Service
Core *Core
CountOnline int
CountServices int
Count24Failures uint64
}
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
err := ErrorResponse{}
ExecuteResponse(w, r, "login.html", err)
} else {
fails := CountFailures()
out := dashboard{services, core, CountOnline(), len(services), fails}
ExecuteResponse(w, r, "dashboard.html", out)
}
}
type serviceHandler struct {
Service Service
Auth bool
}
func ServicesHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "services.html", services)
}
func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
service.Delete()
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
service.DeleteFailures()
services, _ = SelectAllServices()
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func IsAuthenticated(r *http.Request) bool {
if os.Getenv("GO_ENV") == "test" {
return true
}
if core == nil {
return false
}
if store == nil {
return false
}
session, err := store.Get(r, cookieKey)
if err != nil {
return false
}
if session.Values["authenticated"] == nil {
return false
}
return session.Values["authenticated"].(bool)
}
func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
emailer := SelectCommunication(1)
r.ParseForm()
emailer.Host = r.PostForm.Get("host")
emailer.Username = r.PostForm.Get("username")
emailer.Password = r.PostForm.Get("password")
emailer.Port = int(StringInt(r.PostForm.Get("port")))
emailer.Var1 = r.PostForm.Get("address")
Update(emailer)
sample := &types.Email{
To: SessionUser(r).Email,
Subject: "Sample Email",
Template: "error.html",
}
AddEmail(sample)
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
CreateAllAssets()
useAssets = true
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
theme := r.PostForm.Get("theme")
variables := r.PostForm.Get("variables")
SaveAsset(theme, "scss/base.scss")
SaveAsset(variables, "scss/variables.scss")
CompileSASS()
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
name := r.PostForm.Get("project")
if name != "" {
core.Name = name
}
description := r.PostForm.Get("description")
if description != core.Description {
core.Description = description
}
style := r.PostForm.Get("style")
if style != core.Style {
core.Style = style
}
footer := r.PostForm.Get("footer")
if footer != core.Footer {
core.Footer = footer
}
domain := r.PostForm.Get("domain")
if domain != core.Domain {
core.Domain = domain
}
core.Update()
OnSettingsSaved(core)
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
core.FetchPluginRepo()
var pluginFields []PluginSelect
for _, p := range allPlugins {
fields := structs.Map(p.GetInfo())
pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(), fields})
}
core.PluginFields = pluginFields
fmt.Println(core.Communications)
ExecuteResponse(w, r, "plugins.html", core)
}
type PluginSelect struct {
Plugin string
Form string
Params map[string]interface{}
}
func PluginSavedHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
vars := mux.Vars(r)
plug := SelectPlugin(vars["name"])
data := make(map[string]string)
for k, v := range r.PostForm {
data[k] = strings.Join(v, "")
}
plug.OnSave(structs.Map(data))
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
func PluginsDownloadHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//vars := mux.Vars(r)
//name := vars["name"]
//DownloadPlugin(name)
LoadConfig()
http.Redirect(w, r, "/plugins", http.StatusSeeOther)
}
func HelpHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "help.html", nil)
}
func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
interval := StringInt(r.PostForm.Get("interval"))
service := SelectService(StringInt(vars["id"]))
checkin := &Checkin{
Service: service.Id,
Interval: interval,
Api: NewSHA1Hash(18),
}
checkin.Create()
fmt.Println(checkin.Create())
ExecuteResponse(w, r, "service.html", service)
}
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
r.ParseForm()
name := r.PostForm.Get("name")
domain := r.PostForm.Get("domain")
method := r.PostForm.Get("method")
expected := r.PostForm.Get("expected")
status, _ := strconv.Atoi(r.PostForm.Get("expected_status"))
interval, _ := strconv.Atoi(r.PostForm.Get("interval"))
port, _ := strconv.Atoi(r.PostForm.Get("port"))
checkType := r.PostForm.Get("check_type")
service = &Service{
Name: name,
Domain: domain,
Method: method,
Expected: expected,
ExpectedStatus: status,
Interval: interval,
Type: checkType,
Port: port,
}
service.Update()
ExecuteResponse(w, r, "service.html", service)
}
func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
var badge []byte
if service.Online {
badge = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="104" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h50v20H54z"/><path fill="url(#b)" d="M0 0h104v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">` + service.Name + `</text><text x="28" y="14">` + service.Name + `</text><text x="78" y="15" fill="#010101" fill-opacity=".3">online</text><text x="78" y="14">online</text></g></svg>`)
} else {
badge = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="99" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#e05d44" d="M54 0h45v20H54z"/><path fill="url(#b)" d="M0 0h99v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">` + service.Name + `</text><text x="28" y="14">` + service.Name + `</text><text x="75.5" y="15" fill="#010101" fill-opacity=".3">offline</text><text x="75.5" y="14">offline</text></g></svg>`)
}
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write(badge)
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
ExecuteResponse(w, r, "service.html", service)
}
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
nav, _ := tmplBox.String("nav.html")
footer, _ := tmplBox.String("footer.html")
render, err := tmplBox.String(file)
if err != nil {
panic(err)
}
t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
"safe": func(html string) template.HTML {
return template.HTML(html)
},
"Auth": func() bool {
return IsAuthenticated(r)
},
"VERSION": func() string {
return VERSION
},
"underscore": func(html string) string {
return UnderScoreString(html)
},
"User": func() *User {
return SessionUser(r)
},
})
t, _ = t.Parse(nav)
t, _ = t.Parse(footer)
t.Parse(render)
t.Execute(w, data)
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
users, _ := SelectAllUsers()
ExecuteResponse(w, r, "users.html", users)
}
func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
user, _ := SelectUser(int64(id))
users, _ := SelectAllUsers()
if len(users) == 1 {
http.Redirect(w, r, "/users", http.StatusSeeOther)
return
}
user.Delete()
http.Redirect(w, r, "/users", http.StatusSeeOther)
}
func UnderScoreString(str string) string {
// convert every letter to lower case
newStr := strings.ToLower(str)
// convert all spaces/tab to underscore
regExp := regexp.MustCompile("[[:space:][:blank:]]")
newStrByte := regExp.ReplaceAll([]byte(newStr), []byte("_"))
regExp = regexp.MustCompile("`[^a-z0-9]`i")
newStrByte = regExp.ReplaceAll(newStrByte, []byte("_"))
regExp = regexp.MustCompile("[!/']")
newStrByte = regExp.ReplaceAll(newStrByte, []byte("_"))
// and remove underscore from beginning and ending
newStr = strings.TrimPrefix(string(newStrByte), "_")
newStr = strings.TrimSuffix(newStr, "_")
return newStr
}

View File

@ -1,75 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestServiceUrl(t *testing.T) {
t.SkipNow()
req, err := http.NewRequest("GET", "/service/1", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Equal(t, 28, len(rr.Body.Bytes()), "should be balance")
}
func TestApiAllServiceUrl(t *testing.T) {
t.SkipNow()
req, err := http.NewRequest("GET", "/api/services", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
var data []Service
json.Unmarshal(rr.Body.Bytes(), &data)
assert.Equal(t, "Google", data[0].Name, "should be balance")
}
func TestApiServiceUrl(t *testing.T) {
t.SkipNow()
req, err := http.NewRequest("GET", "/api/services/1", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
var data Service
json.Unmarshal(rr.Body.Bytes(), &data)
assert.Equal(t, "Google", data.Name, "should be balance")
}
func TestApiServiceUpdateUrl(t *testing.T) {
t.SkipNow()
payload := []byte(`{"name":"test product - updated name","price":11.22}`)
req, err := http.NewRequest("POST", "/api/services/1", bytes.NewBuffer(payload))
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
var data Service
json.Unmarshal(rr.Body.Bytes(), &data)
assert.Equal(t, "Google", data.Name, "should be balance")
}
func TestApiUserUrl(t *testing.T) {
t.SkipNow()
req, err := http.NewRequest("GET", "/api/users/1", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
var data User
json.Unmarshal(rr.Body.Bytes(), &data)
assert.Equal(t, "testuserhere", data.Username, "should be balance")
}
func TestApiAllUsersUrl(t *testing.T) {
t.SkipNow()
req, err := http.NewRequest("GET", "/api/users", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
var data []User
json.Unmarshal(rr.Body.Bytes(), &data)
assert.Equal(t, "testuserhere", data[0].Username, "should be balance")
}