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

View File

@ -2,10 +2,10 @@
# RENDERING CSS
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
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
rice embed-go

View File

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

View File

@ -1,6 +1,6 @@
FROM alpine:latest
ENV VERSION=v0.27.4
ENV VERSION=v0.27.6
RUN apk --no-cache add libstdc++ ca-certificates
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 (
"fmt"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv"
"time"
)
func CatchCLI(args []string) {
@ -13,65 +13,67 @@ func CatchCLI(args []string) {
fmt.Printf("Statup v%v\n", VERSION)
case "assets":
RenderBoxes()
CreateAllAssets()
core.CreateAllAssets()
case "sass":
CompileSASS()
core.CompileSASS()
case "export":
var err error
fmt.Printf("Statup v%v Exporting Static 'index.html' page...\n", VERSION)
RenderBoxes()
configs, err = LoadConfig()
core.Configs, err = core.LoadConfig()
if err != nil {
log.Send(3, "config.yml file not found")
utils.Log(4, "config.yml file not found")
}
setupMode = true
mainProcess()
time.Sleep(10 * time.Second)
indexSource := ExportIndexHTML()
err = SaveFile("./index.html", []byte(indexSource))
RunOnce()
indexSource := core.ExportIndexHTML()
err = core.SaveFile("./index.html", []byte(indexSource))
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":
HelpEcho()
case "update":
fmt.Println("Sorry updating isn't available yet!")
case "run":
log.Send(1, "Running 1 time and saving to database...")
var err error
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)
}
utils.Log(1, "Running 1 time and saving to database...")
RunOnce()
fmt.Println("Check is complete.")
case "env":
fmt.Println("Statup Environment Variables")
envs, err := godotenv.Read(".env")
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 {
fmt.Printf("%v=%v\n", k, e)
}
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 (
"fmt"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"io/ioutil"
"net/http"
"regexp"
"time"
)
type FailureData types.FailureData
func CheckServices() {
services, _ = SelectAllServices()
log.Send(1, fmt.Sprintf("Loaded %v Services", len(services)))
for _, v := range services {
CoreApp.Services, _ = SelectAllServices()
utils.Log(1, fmt.Sprintf("Loaded %v Services", len(CoreApp.Services)))
for _, v := range CoreApp.Services {
obj := v
go obj.StartCheckins()
go obj.CheckQueue()
@ -26,7 +29,7 @@ func (s *Service) CheckQueue() {
s.Interval = 1
}
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)
}
@ -80,17 +83,13 @@ func (s *Service) Record(response *http.Response) {
OnSuccess(s)
}
type FailureData struct {
Issue string
}
func (s *Service) Failure(issue string) {
s.Online = false
data := FailureData{
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)
SendFailureEmail(s)
//SendFailureEmail(s)
OnFailure(s)
}

View File

@ -1,25 +1,33 @@
package main
package core
import (
"fmt"
"github.com/ararog/timeago"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"time"
)
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 Checkin types.Checkin
func (c *Checkin) String() string {
return c.Api
}
func FindCheckin(api string) *Checkin {
for _, s := range CoreApp.Services {
for _, c := range s.Checkins {
if c.Api == api {
return c
}
}
}
return nil
}
func (s *Service) SelectAllCheckins() []*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)
s.Checkins = checkins
return checkins
@ -27,9 +35,9 @@ func (s *Service) SelectAllCheckins() []*Checkin {
func (u *Checkin) Create() (int64, error) {
u.CreatedAt = time.Now()
uuid, err := dbSession.Collection("checkins").Insert(u)
uuid, err := DbSession.Collection("checkins").Insert(u)
if uuid == nil {
log.Send(2, err)
utils.Log(2, err)
return 0, err
}
fmt.Println("new checkin: ", uuid)
@ -38,7 +46,7 @@ func (u *Checkin) Create() (int64, error) {
func SelectCheckinApi(api string) *Checkin {
var checkin *Checkin
dbSession.Collection("checkins").Find("api", api).One(&checkin)
DbSession.Collection("checkins").Find("api", api).One(&checkin)
return checkin
}
@ -70,17 +78,6 @@ func (f *Checkin) Ago() string {
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() {
if c.Interval == 0 {
return
@ -104,7 +101,7 @@ func (s *Service) StartCheckins() {
}
func CheckinProcess() {
for _, s := range services {
for _, s := range CoreApp.Services {
for _, c := range s.Checkins {
checkin := c
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 (
"github.com/fatih/structs"
@ -7,55 +7,55 @@ import (
)
func OnLoad(db sqlbuilder.Database) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnLoad(db)
}
}
func OnSuccess(s *Service) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnSuccess(structs.Map(s))
}
}
func OnFailure(s *Service) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnFailure(structs.Map(s))
}
}
func OnSettingsSaved(c *Core) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnSettingsSaved(structs.Map(c))
}
}
func OnNewUser(u *User) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnNewUser(structs.Map(u))
}
}
func OnNewService(s *Service) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnNewService(structs.Map(s))
}
}
func OnDeletedService(s *Service) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnDeletedService(structs.Map(s))
}
}
func OnUpdateService(s *Service) {
for _, p := range allPlugins {
for _, p := range AllPlugins {
p.OnUpdatedService(structs.Map(s))
}
}
func SelectPlugin(name string) plugin.PluginActions {
for _, p := range allPlugins {
for _, p := range AllPlugins {
if p.GetInfo().Name == name {
return p
}

View File

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

View File

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

View File

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

View File

@ -1,17 +1,16 @@
package main
package core
import (
"encoding/json"
"fmt"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"strconv"
"time"
"upper.io/db.v3"
)
var (
services []*Service
)
type Failure types.Failure
type Service struct {
Id int64 `db:"id,omitempty" json:"id"`
@ -40,11 +39,11 @@ type Service struct {
}
func serviceCol() db.Collection {
return dbSession.Collection("services")
return DbSession.Collection("services")
}
func SelectService(id int64) *Service {
for _, s := range services {
for _, s := range CoreApp.Services {
if s.Id == id {
return s
}
@ -56,10 +55,14 @@ func SelectAllServices() ([]*Service, error) {
var srvcs []*Service
col := serviceCol().Find()
err := col.All(&srvcs)
if err != nil {
utils.Log(3, err)
}
for _, s := range srvcs {
s.Checkins = s.SelectAllCheckins()
s.Failures = s.SelectAllFailures()
}
CoreApp.Services = srvcs
return srvcs, err
}
@ -106,9 +109,9 @@ func (s *Service) GraphData() string {
increment := "minute"
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))
dated, err := dbSession.Query(db.Raw(sql))
dated, err := DbSession.Query(db.Raw(sql))
if err != nil {
log.Send(2, err)
utils.Log(2, err)
return ""
}
for dated.Next() {
@ -120,7 +123,7 @@ func (s *Service) GraphData() string {
}
data, err := json.Marshal(d)
if err != nil {
log.Send(2, err)
utils.Log(2, err)
return ""
}
return string(data)
@ -151,18 +154,22 @@ func (s *Service) AvgUptime() string {
func (u *Service) RemoveArray() []*Service {
var srvcs []*Service
for _, s := range services {
for _, s := range CoreApp.Services {
if s.Id != u.Id {
srvcs = append(srvcs, s)
}
}
services = srvcs
CoreApp.Services = srvcs
return srvcs
}
func (u *Service) Delete() error {
res := serviceCol().Find("id", u.Id)
err := res.Delete()
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", u.Name, err))
return err
}
u.RemoveArray()
OnDeletedService(u)
return err
@ -176,10 +183,11 @@ func (u *Service) Create() (int64, error) {
u.CreatedAt = time.Now()
uuid, err := serviceCol().Insert(u)
if uuid == nil {
utils.Log(3, fmt.Sprintf("Failed to create service %v. %v", u.Name, err))
return 0, err
}
u.Id = uuid.(int64)
services = append(services, u)
CoreApp.Services = append(CoreApp.Services, u)
go u.CheckQueue()
OnNewService(u)
return uuid.(int64), err
@ -187,7 +195,7 @@ func (u *Service) Create() (int64, error) {
func CountOnline() int {
amount := 0
for _, v := range services {
for _, v := range CoreApp.Services {
if v.Online {
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"
"crypto/tls"
"fmt"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"gopkg.in/gomail.v2"
"html/template"
"time"
@ -15,14 +16,16 @@ var (
emailQue *Que
)
type Email types.Email
type Que struct {
Mailer *gomail.Dialer
Outgoing []*types.Email
Outgoing []*Email
LastSent int
LastSentTime time.Time
}
func AddEmail(email *types.Email) {
func AddEmail(email *Email) {
if emailQue == nil {
return
}
@ -33,12 +36,12 @@ func EmailerQueue() {
if emailQue == nil {
return
}
uniques := []*types.Email{}
uniques := []*Email{}
for _, out := range emailQue.Outgoing {
if isUnique(uniques, out) {
msg := fmt.Sprintf("sending email to: %v \n", out.To)
Send(out)
log.Send(0, msg)
utils.Log(0, msg)
uniques = append(uniques, out)
}
}
@ -48,7 +51,7 @@ func EmailerQueue() {
EmailerQueue()
}
func isUnique(arr []*types.Email, obj *types.Email) bool {
func isUnique(arr []*Email, obj *Email) bool {
for _, v := range arr {
if v.To == obj.To && v.Subject == obj.Subject {
return false
@ -57,7 +60,7 @@ func isUnique(arr []*types.Email, obj *types.Email) bool {
return true
}
func Send(em *types.Email) {
func Send(em *Email) {
source := EmailTemplate(em.Template, em.Data)
m := gomail.NewMessage()
m.SetHeader("From", "info@betatude.com")
@ -65,14 +68,14 @@ func Send(em *types.Email) {
m.SetHeader("Subject", em.Subject)
m.SetBody("text/html", source)
if err := emailQue.Mailer.DialAndSend(m); err != nil {
log.Send(2, err)
utils.Log(2, err)
}
emailQue.LastSent++
emailQue.LastSentTime = time.Now()
}
func SendFailureEmail(service *Service) {
email := &types.Email{
func SendFailureEmail(service *core.Service) {
email := &Email{
To: "info@socialeck.com",
Subject: fmt.Sprintf("Service %v is Failing", service.Name),
Template: "failure.html",
@ -81,30 +84,30 @@ func SendFailureEmail(service *Service) {
AddEmail(email)
}
func LoadMailer(config *types.Communication) *gomail.Dialer {
func LoadMailer(config *core.Communication) *gomail.Dialer {
if config.Host == "" || config.Username == "" || config.Password == "" {
return nil
}
emailQue = new(Que)
emailQue.Outgoing = []*types.Email{}
emailQue.Outgoing = []*Email{}
emailQue.Mailer = gomail.NewDialer(config.Host, config.Port, config.Username, config.Password)
emailQue.Mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
return emailQue.Mailer
}
func EmailTemplate(tmpl string, data interface{}) string {
emailTpl, err := emailBox.String(tmpl)
emailTpl, err := core.EmailBox.String(tmpl)
if err != nil {
log.Send(3, err)
utils.Log(3, err)
}
t := template.New("email")
t, err = t.Parse(emailTpl)
if err != nil {
log.Send(3, err)
utils.Log(3, err)
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil {
log.Send(2, err)
utils.Log(2, err)
}
result := tpl.String()
return result

View File

@ -1,21 +1,20 @@
package main
package handlers
import (
"crypto/sha1"
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"math/rand"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/utils"
"net/http"
)
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) {
vars := mux.Vars(r)
checkin := FindCheckin(vars["api"])
checkin := core.FindCheckin(vars["api"])
checkin.Receivehit()
w.WriteHeader(http.StatusOK)
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) {
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
service := core.SelectService(utils.StringInt(vars["id"]))
json.NewEncoder(w).Encode(service)
}
func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
var s Service
service := core.SelectService(utils.StringInt(vars["id"]))
var s core.Service
decoder := json.NewDecoder(r.Body)
decoder.Decode(&s)
json.NewEncoder(w).Encode(service)
}
func ApiAllServicesHandler(w http.ResponseWriter, r *http.Request) {
services, _ := SelectAllServices()
services, _ := core.SelectAllServices()
json.NewEncoder(w).Encode(services)
}
func ApiUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
user, _ := SelectUser(StringInt(vars["id"]))
user, _ := core.SelectUser(utils.StringInt(vars["id"]))
json.NewEncoder(w).Encode(user)
}
func ApiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
users, _ := SelectAllUsers()
users, _ := core.SelectAllUsers()
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
import (
"encoding/json"
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/go-yaml/yaml"
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/log"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/utils"
"github.com/joho/godotenv"
"golang.org/x/crypto/bcrypt"
"io"
"io/ioutil"
"net/http"
"os"
plg "plugin"
"strconv"
"strings"
)
var (
configs *Config
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
VERSION string
)
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() {
LoadDotEnvs()
}
func LoadDotEnvs() {
err := godotenv.Load()
if err != nil {
fmt.Println("Error loading .env file")
}
}
func main() {
defer logFile.Close()
var err error
if len(os.Args) >= 2 {
CatchCLI(os.Args)
os.Exit(0)
}
var err error
fmt.Printf("Starting Statup v%v\n", VERSION)
utils.Log(1, fmt.Sprintf("Starting Statup v%v\n", VERSION))
RenderBoxes()
hasAssets()
core.HasAssets()
configs, err = LoadConfig()
core.Configs, err = core.LoadConfig()
if err != nil {
log.Send(1, "config.yml file not found - starting in setup mode")
setupMode = true
RunHTTPServer()
utils.Log(2, "config.yml file not found - starting in setup mode")
core.SetupMode = true
handlers.RunHTTPServer()
}
mainProcess()
}
func StringInt(s string) int64 {
num, _ := strconv.Atoi(s)
return int64(num)
func RenderBoxes() {
core.SqlBox = rice.MustFindBox("source/sql")
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() {
var err error
err = DbConnection(configs.Connection)
err = core.DbConnection(core.Configs.Connection)
if err != nil {
throw(err)
utils.Log(3, err)
}
RunDatabaseUpgrades()
core, err = SelectCore()
core.RunDatabaseUpgrades()
core.CoreApp, err = core.SelectCore()
if err != nil {
log.Send(1, "Core database was not found, Statup is not setup yet.")
RunHTTPServer()
utils.Log(2, "Core database was not found, Statup is not setup yet.")
handlers.RunHTTPServer()
}
CheckServices()
core.Communications, _ = SelectAllCommunications()
LoadDefaultCommunications()
core.CheckServices()
core.CoreApp.Communications, err = core.SelectAllCommunications()
if err != nil {
utils.Log(2, err)
}
core.LoadDefaultCommunications()
go DatabaseMaintence()
go core.DatabaseMaintence()
if !setupMode {
if !core.SetupMode {
LoadPlugins()
RunHTTPServer()
handlers.RunHTTPServer()
}
}
func throw(err error) {
fmt.Println("ERROR: ", err)
os.Exit(1)
}
func ForEachPlugin() {
if len(core.Plugins) > 0 {
if len(core.CoreApp.Plugins) > 0 {
//for _, p := range core.Plugins {
// p.OnShutdown()
//}
@ -179,7 +102,7 @@ func LoadPlugins() {
files, err := ioutil.ReadDir("./plugins")
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
}
for _, f := range files {
@ -192,7 +115,7 @@ func LoadPlugins() {
}
plug, err := plg.Open("plugins/" + f.Name())
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
}
symPlugin, err := plug.Lookup("Plugin")
@ -200,42 +123,16 @@ func LoadPlugins() {
var plugActions plugin.PluginActions
plugActions, ok := symPlugin.(plugin.PluginActions)
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
}
allPlugins = append(allPlugins, plugActions)
core.Plugins = append(core.Plugins, plugActions.GetInfo())
//allPlugins = append(allPlugins, plugActions)
core.CoreApp.Plugins = append(core.CoreApp.Plugins, plugActions.GetInfo())
}
OnLoad(dbSession)
fmt.Printf("Loaded %v Plugins\n", len(allPlugins))
core.OnLoad(core.DbSession)
//utils.Log(1, fmt.Sprintf("Loaded %v Plugins\n", len(allPlugins)))
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 (
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/rendon/testcli"
"github.com/stretchr/testify/assert"
"net/http"
@ -19,19 +21,19 @@ var (
)
func init() {
route = Router()
route = handlers.Router()
}
func TestInit(t *testing.T) {
RenderBoxes()
os.Remove("./statup.db")
Router()
handlers.Router()
LoadDotEnvs()
}
func TestMySQLMakeConfig(t *testing.T) {
config := &DbConfig{
config := &core.DbConfig{
"mysql",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
@ -49,30 +51,30 @@ func TestMySQLMakeConfig(t *testing.T) {
err := config.Save()
assert.Nil(t, err)
_, err = LoadConfig()
_, err = core.LoadConfig()
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)
InsertDefaultComms()
core.InsertDefaultComms()
}
func TestInsertMysqlSample(t *testing.T) {
err := LoadSampleData()
err := core.LoadSampleData()
assert.Nil(t, err)
}
func TestSelectCoreMYQL(t *testing.T) {
var err error
core, err = SelectCore()
core.CoreApp, err = core.SelectCore()
assert.Nil(t, err)
assert.Equal(t, "Testing MYSQL", core.Name)
assert.Equal(t, VERSION, core.Version)
assert.Equal(t, "Testing MYSQL", core.CoreApp.Name)
assert.Equal(t, VERSION, core.CoreApp.Version)
}
func TestSqliteMakeConfig(t *testing.T) {
config := &DbConfig{
config := &core.DbConfig{
"sqlite",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
@ -90,22 +92,22 @@ func TestSqliteMakeConfig(t *testing.T) {
err := config.Save()
assert.Nil(t, err)
_, err = LoadConfig()
_, err = core.LoadConfig()
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)
InsertDefaultComms()
core.InsertDefaultComms()
}
func TestInsertSqliteSample(t *testing.T) {
err := LoadSampleData()
err := core.LoadSampleData()
assert.Nil(t, err)
}
func TestPostgresMakeConfig(t *testing.T) {
config := &DbConfig{
config := &core.DbConfig{
"postgres",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
@ -123,38 +125,38 @@ func TestPostgresMakeConfig(t *testing.T) {
err := config.Save()
assert.Nil(t, err)
_, err = LoadConfig()
_, err = core.LoadConfig()
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)
InsertDefaultComms()
core.InsertDefaultComms()
}
func TestInsertPostgresSample(t *testing.T) {
err := LoadSampleData()
err := core.LoadSampleData()
assert.Nil(t, err)
}
func TestSelectCorePostgres(t *testing.T) {
var err error
core, err = SelectCore()
core.CoreApp, err = core.SelectCore()
assert.Nil(t, err)
assert.Equal(t, "Testing POSTGRES", core.Name)
assert.Equal(t, VERSION, core.Version)
assert.Equal(t, "Testing POSTGRES", core.CoreApp.Name)
assert.Equal(t, VERSION, core.CoreApp.Version)
}
func TestSelectCore(t *testing.T) {
var err error
core, err = SelectCore()
core.CoreApp, err = core.SelectCore()
assert.Nil(t, err)
assert.Equal(t, "Testing POSTGRES", core.Name)
assert.Equal(t, VERSION, core.Version)
assert.Equal(t, "Testing POSTGRES", core.CoreApp.Name)
assert.Equal(t, VERSION, core.CoreApp.Version)
}
func TestUser_Create(t *testing.T) {
user := &User{
user := &core.User{
Username: "admin",
Password: "admin",
Email: "info@testuser.com",
@ -164,14 +166,22 @@ func TestUser_Create(t *testing.T) {
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) {
service := SelectService(1)
service := core.SelectService(1)
assert.NotNil(t, service)
t.Log(service)
assert.Equal(t, "Google", service.Name)
}
func TestService_Create(t *testing.T) {
service := &Service{
service := &core.Service{
Name: "test service",
Domain: "https://google.com",
ExpectedStatus: 200,
@ -186,7 +196,7 @@ func TestService_Create(t *testing.T) {
}
func TestService_Check(t *testing.T) {
service := SelectService(2)
service := core.SelectService(2)
assert.NotNil(t, service)
assert.Equal(t, "Statup.io", service.Name)
out := service.Check()
@ -194,28 +204,28 @@ func TestService_Check(t *testing.T) {
}
func TestService_AvgTime(t *testing.T) {
service := SelectService(1)
service := core.SelectService(1)
assert.NotNil(t, service)
avg := service.AvgUptime()
assert.Equal(t, "100", avg)
}
func TestService_Online24(t *testing.T) {
service := SelectService(1)
service := core.SelectService(1)
assert.NotNil(t, service)
online := service.Online24()
assert.Equal(t, float32(100), online)
}
func TestService_GraphData(t *testing.T) {
service := SelectService(1)
service := core.SelectService(1)
assert.NotNil(t, service)
data := service.GraphData()
assert.NotEmpty(t, data)
}
func TestBadService_Create(t *testing.T) {
service := &Service{
service := &core.Service{
Name: "bad service",
Domain: "https://9839f83h72gey2g29278hd2od2d.com",
ExpectedStatus: 200,
@ -230,13 +240,13 @@ func TestBadService_Create(t *testing.T) {
}
func TestBadService_Check(t *testing.T) {
service := SelectService(4)
service := core.SelectService(4)
assert.NotNil(t, service)
assert.Equal(t, "Github Failing Check", service.Name)
}
func TestService_Hits(t *testing.T) {
service := SelectService(1)
service := core.SelectService(1)
assert.NotNil(t, service)
hits, err := service.Hits()
assert.Nil(t, err)
@ -244,7 +254,7 @@ func TestService_Hits(t *testing.T) {
}
func TestService_LimitedHits(t *testing.T) {
service := SelectService(1)
service := core.SelectService(1)
assert.NotNil(t, service)
hits, err := service.LimitedHits()
assert.Nil(t, err)
@ -273,7 +283,7 @@ func TestPrometheusHandler(t *testing.T) {
rr := httptest.NewRecorder()
route.ServeHTTP(rr, req)
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) {
@ -348,7 +358,7 @@ func TestExportCommand(t *testing.T) {
c := testcli.Command("statup", "export")
c.Run()
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) {

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 {
background-color: #fcfcfc; }
background-color: #fcfcfc;
}
.container {
padding-top: 20px;
padding-bottom: 20px;
max-width: 860px; }
max-width: 860px;
}
.online_list .badge {
margin-top: 0.2rem; }
margin-top: 0.2rem;
}
.navbar {
margin-bottom: 30px; }
margin-bottom: 30px;
}
.btn-sm {
line-height: 1.3;
font-size: 0.75rem; }
font-size: 0.75rem;
}
.view_service_btn {
position: absolute;
bottom: -40px;
right: 40px; }
right: 40px;
}
.service_lower_info {
position: absolute;
bottom: -40px;
left: 40px;
color: #d1ffca;
font-size: 0.85rem; }
font-size: 0.85rem;
}
.lg_number {
font-size: 26pt;
font-weight: bold;
display: block;
color: #474747; }
color: #474747;
}
.stats_area {
text-align: center;
color: #a5a5a5; }
color: #a5a5a5;
}
.lower_canvas {
height: 55px;
width: 100%;
background-color: #48d338;
padding: 17px 10px; }
padding: 17px 10px;
}
.lower_canvas SPAN {
font-size: 1rem; }
font-size: 1rem;
}
.footer {
text-decoration: none;
margin-top: 20px; }
margin-top: 20px;
}
.footer A {
color: #aaaaaa;
text-decoration: none; }
text-decoration: none;
}
.footer A:HOVER {
color: #6d6d6d; }
color: #6d6d6d;
}
.online_badge {
color: #fff;
background-color: #35b317; }
background-color: #35b317;
}
.offline_badge {
color: #fff;
background-color: #c51919; }
background-color: #c51919;
}
.progress {
margin-top: -20px;
@ -72,22 +88,27 @@ HTML, BODY {
margin-bottom: 15px;
width: calc(100% + 40px);
height: 3px;
border-radius: 0; }
border-radius: 0;
}
.card {
background-color: #fff; }
background-color: #fff;
}
.card-body {
overflow: hidden; }
overflow: hidden;
}
.card-body H4 A {
color: #239e07;
text-decoration: none; }
text-decoration: none;
}
.chart-container {
position: relative;
height: 170px;
width: 100%; }
width: 100%;
}
.CodeMirror {
/* Bootstrap Settings */
@ -107,81 +128,100 @@ HTML, BODY {
border: 1px solid #ccc;
border-radius: 4px;
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 */
font-family: monospace;
position: relative;
overflow: hidden;
height: 60vh; }
height: 60vh;
}
.CodeMirror-focused {
/* Bootstrap Settings */
border-color: #66afe9;
outline: 0;
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) {
.sm-container {
margin-top: 40px !important;
padding: 0 !important; }
padding: 0 !important;
}
.list-group-item H5 {
font-size: 0.9rem; }
font-size: 0.9rem;
}
.container {
padding: 0 !important; }
padding: 0 !important;
}
.navbar {
margin-left: 0px;
margin-top: 0px;
width: 100%;
margin-bottom: 0; }
margin-bottom: 0;
}
.card-body {
font-size: 6pt;
padding: 5px 5px; }
padding: 5px 5px;
}
.lg_number {
font-size: 1.5rem; }
font-size: 1.5rem;
}
.stats_area {
margin-top: 35px !important;
margin-bottom: 35px !important; }
margin-bottom: 35px !important;
}
.stats_area .col-4 {
padding-left: 0;
padding-right: 0; }
padding-right: 0;
}
.lower_canvas SPAN {
font-size: 0.9rem;
float: left; }
float: left;
}
.btn-sm {
line-height: 0.9rem;
font-size: 0.65rem; }
font-size: 0.65rem;
}
.full-col-12 {
padding-left: 0px;
padding-right: 0px; }
padding-right: 0px;
}
.card {
border: 0;
border-radius: 0; }
border-radius: 0;
}
.list-group-item {
border-top: 1px solid #e4e4e4;
border: 0px; }
border: 0px;
}
.list-group-item:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0; }
border-top-right-radius: 0;
}
.list-group-item:last-child {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0; }
border-bottom-left-radius: 0;
}
.list-group-item P {
font-size: 0.7rem; } }
font-size: 0.7rem;
}
}
/*# 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 ranTheme = false;
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) {

View File

@ -1,10 +1,6 @@
var currentLocation = window.location;
$("#domain_input").val(currentLocation.origin);
function forceLower(strInput) {
strInput.value=strInput.value.toLowerCase();
}
$('select#database_type').on('change', function(){
var selected = $('#database_type option:selected').val();
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">
{{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://statup.io/base.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<link rel="stylesheet" href="https://assets.statup.io/bootstrap.min.css">
{{ else }}
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css">
<script src="/js/Chart.bundle.min.js"></script>
{{end}}
<link rel="stylesheet" href="/css/base.css">
<title>{{.Core.Name}} Status</title>
</head>
<body>
@ -96,6 +94,17 @@
{{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>
{{ range .Services }}
{{ if .AvgTime }}
@ -247,6 +256,7 @@ var chartdata = new Chart(ctx, {
{{ end }}
</script>
<script src="/js/main.js"></script>
{{ if .Core.Style }}
<style>
@ -255,14 +265,5 @@ var chartdata = new Chart(ctx, {
{{ 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>
</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">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css">
<script src="/js/Chart.bundle.min.js"></script>
<title>Statup | Setup</title>
</head>
@ -77,7 +76,7 @@
<div class="form-group">
<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 class="form-group">

View File

@ -1,6 +1,44 @@
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 {
Id int64 `db:"id,omitempty" json:"id"`
@ -25,3 +63,45 @@ type Email struct {
Template string
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")
}