diff --git a/.travis.yml b/.travis.yml index 00eb1a51..7a50ae5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/.travis/compile.sh b/.travis/compile.sh index 9d527ee0..0d3f868f 100755 --- a/.travis/compile.sh +++ b/.travis/compile.sh @@ -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 diff --git a/.travis/deploy.sh b/.travis/deploy.sh index 95a93a03..2b8ef9dd 100755 --- a/.travis/deploy.sh +++ b/.travis/deploy.sh @@ -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 diff --git a/Dockerfile b/Dockerfile index 8d501907..b04b15a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ diff --git a/assets.go b/assets.go deleted file mode 100644 index e3406467..00000000 --- a/assets.go +++ /dev/null @@ -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") -} diff --git a/cli.go b/cli.go index ec428c04..d84c39ab 100644 --- a/cli.go +++ b/cli.go @@ -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) } } diff --git a/communication.go b/communication.go deleted file mode 100644 index e56989e9..00000000 --- a/communication.go +++ /dev/null @@ -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 -} diff --git a/core.go b/core.go deleted file mode 100644 index 8d8d3fd0..00000000 --- a/core.go +++ /dev/null @@ -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 -} diff --git a/core/assets.go b/core/assets.go new file mode 100644 index 00000000..f42fa580 --- /dev/null +++ b/core/assets.go @@ -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") + } +} diff --git a/checker.go b/core/checker.go similarity index 82% rename from checker.go rename to core/checker.go index da70be3b..3f03776c 100644 --- a/checker.go +++ b/core/checker.go @@ -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) } diff --git a/checkin.go b/core/checkin.go similarity index 73% rename from checkin.go rename to core/checkin.go index 8df4d0ea..0aa6ef70 100644 --- a/checkin.go +++ b/core/checkin.go @@ -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() diff --git a/core/communication.go b/core/communication.go new file mode 100644 index 00000000..87b8160b --- /dev/null +++ b/core/communication.go @@ -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 +} diff --git a/core/configs.go b/core/configs.go new file mode 100644 index 00000000..b4055bc0 --- /dev/null +++ b/core/configs.go @@ -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 +} diff --git a/core/core.go b/core/core.go new file mode 100644 index 00000000..5fdaf241 --- /dev/null +++ b/core/core.go @@ -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 +} diff --git a/core/database.go b/core/database.go new file mode 100644 index 00000000..fdbd1fbb --- /dev/null +++ b/core/database.go @@ -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 +} diff --git a/events.go b/core/events.go similarity index 72% rename from events.go rename to core/events.go index a80d75e8..2fef5746 100644 --- a/events.go +++ b/core/events.go @@ -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 } diff --git a/utils.go b/core/export.go similarity index 61% rename from utils.go rename to core/export.go index f687c2ee..36208e27 100644 --- a/utils.go +++ b/core/export.go @@ -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 diff --git a/failures.go b/core/failures.go similarity index 72% rename from failures.go rename to core/failures.go index 7f7e0956..995f8dc8 100644 --- a/failures.go +++ b/core/failures.go @@ -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 } diff --git a/hits.go b/core/hits.go similarity index 81% rename from hits.go rename to core/hits.go index 997e9c26..c2d94cd1 100644 --- a/hits.go +++ b/core/hits.go @@ -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 } diff --git a/services.go b/core/services.go similarity index 85% rename from services.go rename to core/services.go index ffa0e901..aa82e394 100644 --- a/services.go +++ b/core/services.go @@ -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++ } diff --git a/core/setup.go b/core/setup.go new file mode 100644 index 00000000..53c87362 --- /dev/null +++ b/core/setup.go @@ -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 +} diff --git a/core/users.go b/core/users.go new file mode 100644 index 00000000..e8c019bb --- /dev/null +++ b/core/users.go @@ -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 +} diff --git a/database.go b/database.go deleted file mode 100644 index 8c943e48..00000000 --- a/database.go +++ /dev/null @@ -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() { - -} diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 2ce18f83..00000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -statup.io \ No newline at end of file diff --git a/docs/base.css b/docs/base.css deleted file mode 100644 index 78b83a45..00000000 --- a/docs/base.css +++ /dev/null @@ -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; - } - - -} \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index b5754e20..00000000 --- a/docs/index.html +++ /dev/null @@ -1 +0,0 @@ -ok \ No newline at end of file diff --git a/docs/install.sh b/docs/install.sh deleted file mode 100755 index 226fda4e..00000000 --- a/docs/install.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/emailer.go b/emailer.go index 3f7df2a5..545e64e8 100644 --- a/emailer.go +++ b/emailer.go @@ -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 diff --git a/api.go b/handlers/api.go similarity index 51% rename from api.go rename to handlers/api.go index b8033119..c64d05a6 100644 --- a/api.go +++ b/handlers/api.go @@ -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) -} diff --git a/handlers/dashboard.go b/handlers/dashboard.go new file mode 100644 index 00000000..ad9fa33a --- /dev/null +++ b/handlers/dashboard.go @@ -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) +} diff --git a/handlers/handlers.go b/handlers/handlers.go new file mode 100644 index 00000000..6b8cde0c --- /dev/null +++ b/handlers/handlers.go @@ -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 diff --git a/handlers/index.go b/handlers/index.go new file mode 100644 index 00000000..65c6b18a --- /dev/null +++ b/handlers/index.go @@ -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) +} diff --git a/handlers/misc.go b/handlers/misc.go new file mode 100644 index 00000000..39e3f2b5 --- /dev/null +++ b/handlers/misc.go @@ -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)) +} diff --git a/handlers/plugins.go b/handlers/plugins.go new file mode 100644 index 00000000..66606310 --- /dev/null +++ b/handlers/plugins.go @@ -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) +} diff --git a/handlers/prometheus.go b/handlers/prometheus.go new file mode 100644 index 00000000..c9363777 --- /dev/null +++ b/handlers/prometheus.go @@ -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)) +} diff --git a/handlers/routes.go b/handlers/routes.go new file mode 100644 index 00000000..fcc5f51d --- /dev/null +++ b/handlers/routes.go @@ -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 +} diff --git a/handlers/services.go b/handlers/services.go new file mode 100644 index 00000000..c80e2638 --- /dev/null +++ b/handlers/services.go @@ -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(`` + service.Name + `` + service.Name + `onlineonline`) + } else { + badge = []byte(`` + service.Name + `` + service.Name + `offlineoffline`) + } + + 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) +} diff --git a/handlers/settings.go b/handlers/settings.go new file mode 100644 index 00000000..b7d67813 --- /dev/null +++ b/handlers/settings.go @@ -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) +} diff --git a/handlers/setup.go b/handlers/setup.go new file mode 100644 index 00000000..c6f71e28 --- /dev/null +++ b/handlers/setup.go @@ -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) +} diff --git a/handlers/users.go b/handlers/users.go new file mode 100644 index 00000000..b7c69218 --- /dev/null +++ b/handlers/users.go @@ -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) +} diff --git a/log/log.go b/log/log.go deleted file mode 100644 index 5a75485a..00000000 --- a/log/log.go +++ /dev/null @@ -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) - } -} diff --git a/main.go b/main.go index cc4be4dc..2f75e442 100644 --- a/main.go +++ b/main.go @@ -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) -} diff --git a/main_test.go b/main_test.go index a23a84d6..a3c8089b 100644 --- a/main_test.go +++ b/main_test.go @@ -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) { diff --git a/setup.go b/setup.go deleted file mode 100644 index ea0d5f5b..00000000 --- a/setup.go +++ /dev/null @@ -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 -} diff --git a/html/css/base.css b/source/css/base.css similarity index 67% rename from html/css/base.css rename to source/css/base.css index f6bf8f19..1dfb0f41 100644 --- a/html/css/base.css +++ b/source/css/base.css @@ -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 */ diff --git a/source/css/base.css.map b/source/css/base.css.map new file mode 100644 index 00000000..3b2b0af3 --- /dev/null +++ b/source/css/base.css.map @@ -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"} \ No newline at end of file diff --git a/html/css/bootstrap.min.css b/source/css/bootstrap.min.css similarity index 100% rename from html/css/bootstrap.min.css rename to source/css/bootstrap.min.css diff --git a/html/emails/error.html b/source/emails/error.html similarity index 100% rename from html/emails/error.html rename to source/emails/error.html diff --git a/html/emails/failure.html b/source/emails/failure.html similarity index 100% rename from html/emails/failure.html rename to source/emails/failure.html diff --git a/html/js/Chart.bundle.min.js b/source/js/Chart.bundle.min.js similarity index 100% rename from html/js/Chart.bundle.min.js rename to source/js/Chart.bundle.min.js diff --git a/html/js/bootstrap.min.js b/source/js/bootstrap.min.js similarity index 100% rename from html/js/bootstrap.min.js rename to source/js/bootstrap.min.js diff --git a/html/js/jquery-3.3.1.slim.min.js b/source/js/jquery-3.3.1.slim.min.js similarity index 100% rename from html/js/jquery-3.3.1.slim.min.js rename to source/js/jquery-3.3.1.slim.min.js diff --git a/html/js/main.js b/source/js/main.js similarity index 87% rename from html/js/main.js rename to source/js/main.js index e9df2027..008b54d5 100644 --- a/html/js/main.js +++ b/source/js/main.js @@ -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) { diff --git a/html/js/setup.js b/source/js/setup.js similarity index 89% rename from html/js/setup.js rename to source/js/setup.js index 0b482d7d..1f4fac3a 100644 --- a/html/js/setup.js +++ b/source/js/setup.js @@ -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") { diff --git a/html/scss/base.scss b/source/scss/base.scss similarity index 100% rename from html/scss/base.scss rename to source/scss/base.scss diff --git a/html/scss/variables.scss b/source/scss/variables.scss similarity index 100% rename from html/scss/variables.scss rename to source/scss/variables.scss diff --git a/sql/down.sql b/source/sql/down.sql similarity index 100% rename from sql/down.sql rename to source/sql/down.sql diff --git a/sql/mysql_up.sql b/source/sql/mysql_up.sql similarity index 100% rename from sql/mysql_up.sql rename to source/sql/mysql_up.sql diff --git a/sql/postgres_up.sql b/source/sql/postgres_up.sql similarity index 100% rename from sql/postgres_up.sql rename to source/sql/postgres_up.sql diff --git a/sql/sqlite_up.sql b/source/sql/sqlite_up.sql similarity index 100% rename from sql/sqlite_up.sql rename to source/sql/sqlite_up.sql diff --git a/sql/upgrade.sql b/source/sql/upgrade.sql similarity index 100% rename from sql/upgrade.sql rename to source/sql/upgrade.sql diff --git a/html/tmpl/dashboard.html b/source/tmpl/dashboard.html similarity index 100% rename from html/tmpl/dashboard.html rename to source/tmpl/dashboard.html diff --git a/html/tmpl/footer.html b/source/tmpl/footer.html similarity index 100% rename from html/tmpl/footer.html rename to source/tmpl/footer.html diff --git a/html/tmpl/help.html b/source/tmpl/help.html similarity index 100% rename from html/tmpl/help.html rename to source/tmpl/help.html diff --git a/html/tmpl/index.html b/source/tmpl/index.html similarity index 90% rename from html/tmpl/index.html rename to source/tmpl/index.html index 31379620..95bddee4 100644 --- a/html/tmpl/index.html +++ b/source/tmpl/index.html @@ -5,14 +5,12 @@ {{if .Core.OfflineAssets}} - - - + {{ else }} - - {{end}} + + {{.Core.Name}} Status @@ -96,6 +94,17 @@ {{template "footer"}} + +{{if .Core.OfflineAssets}} + + + +{{ else }} + + + +{{end}} + + {{ if .Core.Style }}