diff --git a/.gitignore b/.gitignore
index d0671b88..6ca9f5fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.idea
rice-box.go
-config.yml
\ No newline at end of file
+config.yml
+statup.db
+plugins/*.so
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index d7ab77ad..281f3bc1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,9 @@ sudo: required
services:
- docker
+ - postgresql
+ - mysql
+ - mongodb
env:
- VERSION=0.12
@@ -37,14 +40,12 @@ deploy:
- "build/statup-windows-x32.exe"
skip_cleanup: true
-services:
- - postgresql
-
notifications:
email: false
before_install:
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then travis_wait 30 docker pull karalabe/xgo-latest; fi
+ - mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
before_script:
- psql -c 'create database travis_ci_test;' -U postgres
diff --git a/api.go b/api.go
new file mode 100644
index 00000000..f67abb5a
--- /dev/null
+++ b/api.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "encoding/json"
+ "github.com/gorilla/mux"
+ "net/http"
+)
+
+func ApiIndexHandler(w http.ResponseWriter, r *http.Request) {
+ json.NewEncoder(w).Encode(core)
+}
+
+func ApiServiceHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ service, _ := SelectService(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
+ decoder := json.NewDecoder(r.Body)
+ decoder.Decode(&s)
+
+ json.NewEncoder(w).Encode(service)
+}
+
+func ApiAllServicesHandler(w http.ResponseWriter, r *http.Request) {
+ services, _ := SelectAllServices()
+ json.NewEncoder(w).Encode(services)
+}
+
+func ApiUserHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ user, _ := SelectUser(StringInt(vars["id"]))
+ json.NewEncoder(w).Encode(user)
+}
+
+func ApiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
+ users, _ := SelectAllUsers()
+ json.NewEncoder(w).Encode(users)
+}
diff --git a/checker.go b/checker.go
index e4a59a7a..382a1497 100644
--- a/checker.go
+++ b/checker.go
@@ -9,7 +9,7 @@ import (
)
func CheckServices() {
- services = SelectAllServices()
+ services, _ = SelectAllServices()
for _, v := range services {
obj := v
go obj.CheckQueue()
@@ -18,12 +18,15 @@ func CheckServices() {
func (s *Service) CheckQueue() {
s.Check()
+ if s.Interval < 1 {
+ s.Interval = 1
+ }
fmt.Printf(" Service: %v | Online: %v | Latency: %v\n", s.Name, s.Online, s.Latency)
time.Sleep(time.Duration(s.Interval) * time.Second)
s.CheckQueue()
}
-func (s *Service) Check() {
+func (s *Service) Check() *Service {
t1 := time.Now()
client := http.Client{
Timeout: 30 * time.Second,
@@ -33,7 +36,7 @@ func (s *Service) Check() {
s.Latency = t2.Sub(t1).Seconds()
if err != nil {
s.Failure(fmt.Sprintf("HTTP Error %v", err))
- return
+ return s
}
defer response.Body.Close()
if s.Expected != "" {
@@ -41,23 +44,40 @@ func (s *Service) Check() {
match, _ := regexp.MatchString(s.Expected, string(contents))
if !match {
s.Failure(fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
- return
+ return s
}
}
if s.ExpectedStatus != response.StatusCode {
s.Failure(fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus))
- return
+ return s
}
s.Online = true
s.Record(response)
+ return s
+}
+
+type HitData struct {
+ Latency float64
}
func (s *Service) Record(response *http.Response) {
- db.QueryRow("INSERT INTO hits(service,latency,created_at) VALUES($1,$2,NOW()) returning id;", s.Id, s.Latency).Scan()
+ s.Online = true
+ data := HitData{
+ Latency: s.Latency,
+ }
+ s.CreateHit(data)
OnSuccess(s)
}
+type FailureData struct {
+ Issue string
+}
+
func (s *Service) Failure(issue string) {
- db.QueryRow("INSERT INTO failures(issue,service,created_at) VALUES($1,$2,NOW()) returning id;", issue, s.Id).Scan()
+ s.Online = false
+ data := FailureData{
+ Issue: issue,
+ }
+ s.CreateFailure(data)
OnFailure(s)
}
diff --git a/core.go b/core.go
index 82e0a5e2..82bed73b 100644
--- a/core.go
+++ b/core.go
@@ -6,29 +6,23 @@ import (
)
type Core struct {
- Name string
- Config string
- Key string
- Secret string
- Version string
+ Name string `db:"name"`
+ Description string `db:"description"`
+ Config string `db:"config"`
+ ApiKey string `db:"api_key"`
+ ApiSecret string `db:"api_secret"`
+ Version string `db:"version"`
Plugins []plugin.Info
Repos []PluginJSON
PluginFields []PluginSelect
}
-
func SelectCore() (*Core, error) {
var core Core
- rows, err := db.Query("SELECT * FROM core")
+ err := dbSession.Collection("core").Find().One(&core)
if err != nil {
return nil, err
}
- for rows.Next() {
- err = rows.Scan(&core.Name, &core.Config, &core.Key, &core.Secret, &core.Version)
- if err != nil {
- return nil, err
- }
- }
- store = sessions.NewCookieStore([]byte(core.Secret))
+ store = sessions.NewCookieStore([]byte(core.ApiSecret))
return &core, err
}
diff --git a/database.go b/database.go
index 3ad42558..1503a362 100644
--- a/database.go
+++ b/database.go
@@ -1,38 +1,59 @@
package main
import (
- "database/sql"
"fmt"
"github.com/hunterlong/statup/plugin"
- _ "github.com/lib/pq"
- _ "github.com/mattn/go-sqlite3"
- _ "github.com/go-sql-driver/mysql"
- "math/rand"
- "time"
+ "strings"
+ "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
- var dbInfo string
- if dbType=="sqlite3" {
- dbInfo = "./statup.db"
- } else if dbType=="mysql" {
- dbInfo = fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8", configs.User, configs.Password, configs.Host, configs.Port, configs.Database)
+ if dbType == "sqlite" {
+ sqliteSettings = sqlite.ConnectionURL{
+ Database: "statup.db",
+ }
+ dbSession, err = sqlite.Open(sqliteSettings)
+ if err != nil {
+ return err
+ }
+ } else if dbType == "mysql" {
+ 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 {
- dbInfo = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", configs.Host, configs.Port, configs.User, configs.Password, configs.Database)
+ postgresSettings = postgresql.ConnectionURL{
+ Database: configs.Database,
+ Host: configs.Host,
+ User: configs.User,
+ Password: configs.Password,
+ }
+ dbSession, err = postgresql.Open(postgresSettings)
+ if err != nil {
+ return err
+ }
}
- db, err = sql.Open(dbType, dbInfo)
- if err != nil {
- return err
- }
-
- //stmt, err := db.Prepare("CREATE database statup;")
- //if err != nil {
- // panic(err)
- //}
- //stmt.Exec()
-
- plugin.SetDatabase(db)
+ //dbSession.SetLogging(true)
+ dbServer = dbType
+ plugin.SetDatabase(dbSession)
return err
}
@@ -45,33 +66,104 @@ func UpgradeDatabase() {
}
fmt.Println("Upgrading Database...")
upgrade, _ := sqlBox.String("upgrade.sql")
- db.QueryRow(upgrade).Scan()
+ requests := strings.Split(upgrade, ";")
+ for _, request := range requests {
+ _, err := db.Exec(request)
+ if err != nil {
+ fmt.Println(err)
+ }
+ }
}
func DropDatabase() {
fmt.Println("Dropping Tables...")
down, _ := sqlBox.String("down.sql")
- db.QueryRow(down).Scan()
+ requests := strings.Split(down, ";")
+ for _, request := range requests {
+ _, err := dbSession.Exec(request)
+ if err != nil {
+ fmt.Println(err)
+ }
+ }
+}
+
+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",
+ }
+ admin := &User{
+ Username: "admin",
+ Password: "admin",
+ Email: "admin@admin.com",
+ }
+ s1.Create()
+ s2.Create()
+ s3.Create()
+ s4.Create()
+ admin.Create()
+
+ for i := 0; i < 20; i++ {
+ s1.Check()
+ s2.Check()
+ s3.Check()
+ s4.Check()
+ }
+
+ return nil
}
func CreateDatabase() {
fmt.Println("Creating Tables...")
VERSION = "1.1.1"
- up, _ := sqlBox.String("up.sql")
- db.QueryRow(up).Scan()
+ 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 {
+ fmt.Println(err)
+ }
+ }
//secret := NewSHA1Hash()
//db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan()
fmt.Println("Database Created")
//SampleData()
}
-
-func SampleData() {
- i := 0
- for i < 300 {
- ran := rand.Float32()
- latency := fmt.Sprintf("%0.2f", ran)
- date := time.Now().AddDate(0, 0, i)
- db.QueryRow("INSERT INTO hits (service, latency, created_at) VALUES (1, $1, $2);", latency, date).Scan()
- i++
- }
-}
diff --git a/failures.go b/failures.go
index 8261a1e5..7df32f61 100644
--- a/failures.go
+++ b/failures.go
@@ -1,54 +1,53 @@
package main
import (
- "github.com/ararog/timeago"
"time"
)
type Failure struct {
- Id int
- Issue string
- Service int
- CreatedAt time.Time
+ Id int `db:"id,omitempty"`
+ Issue string `db:"issue"`
+ Service int64 `db:"service"`
+ CreatedAt time.Time `db:"created_at"`
Ago string
}
-func (s *Service) SelectAllFailures() []*Failure {
- var tks []*Failure
- rows, err := db.Query("SELECT * FROM failures WHERE service=$1 ORDER BY id DESC LIMIT 10", s.Id)
- if err != nil {
- panic(err)
+func (s *Service) CreateFailure(data FailureData) (int64, error) {
+ fail := &Failure{
+ Issue: data.Issue,
+ Service: s.Id,
+ CreatedAt: time.Now(),
}
- for rows.Next() {
- var tk Failure
- err = rows.Scan(&tk.Id, &tk.Issue, &tk.Service, &tk.CreatedAt)
- if err != nil {
- panic(err)
- }
-
- tk.Ago, _ = timeago.TimeAgoWithTime(time.Now(), tk.CreatedAt)
-
- tks = append(tks, &tk)
+ s.Failures = append(s.Failures, fail)
+ col := dbSession.Collection("failures")
+ uuid, err := col.Insert(fail)
+ if uuid == nil {
+ return 0, err
}
- return tks
+ return uuid.(int64), err
}
-func CountFailures() int {
- var amount int
- db.QueryRow("SELECT COUNT(id) FROM failures;").Scan(&amount)
- return amount
+func (s *Service) SelectAllFailures() ([]*Failure, error) {
+ var fails []*Failure
+ col := dbSession.Collection("failures").Find("session", s.Id)
+ err := col.All(&fails)
+ return fails, err
}
-func (s *Service) TotalFailures() int {
- var amount int
- db.QueryRow("SELECT COUNT(id) FROM failures WHERE service=$1;", s.Id).Scan(&amount)
- return amount
+func CountFailures() (uint64, error) {
+ col := dbSession.Collection("failures").Find()
+ amount, err := col.Count()
+ return amount, err
}
-func (s *Service) TotalFailures24Hours() int {
- var amount int
- t := time.Now()
- x := t.AddDate(0, 0, -1)
- db.QueryRow("SELECT COUNT(id) FROM failures WHERE service=$1 AND created_at>=$2 AND created_at<$3;", s.Id, x, t).Scan(&amount)
- return amount
+func (s *Service) TotalFailures() (uint64, error) {
+ 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)
+ amount, err := col.Count()
+ return amount, err
}
diff --git a/hits.go b/hits.go
index 26dc2cab..04825a3b 100644
--- a/hits.go
+++ b/hits.go
@@ -3,54 +3,51 @@ package main
import "time"
type Hit struct {
- Id int
- Metric int
- Value float64
- CreatedAt time.Time
+ Id int `db:"id,omitempty"`
+ Service int64 `db:"service"`
+ Latency float64 `db:"latency"`
+ CreatedAt time.Time `db:"created_at"`
}
-func (s *Service) Hits() []Hit {
- var tks []Hit
- rows, err := db.Query("SELECT * FROM hits WHERE service=$1 ORDER BY id DESC LIMIT 256", s.Id)
- if err != nil {
- panic(err)
+func (s *Service) CreateHit(d HitData) (int64, error) {
+ h := Hit{
+ Service: s.Id,
+ Latency: d.Latency,
+ CreatedAt: time.Now(),
}
- for rows.Next() {
- var tk Hit
- err = rows.Scan(&tk.Id, &tk.Metric, &tk.Value, &tk.CreatedAt)
- if err != nil {
- panic(err)
- }
- tks = append(tks, tk)
+ col := dbSession.Collection("hits")
+ uuid, err := col.Insert(h)
+ if uuid == nil {
+ return 0, err
}
- return tks
+ return uuid.(int64), err
}
-func (s *Service) SelectHitsGroupBy(group string) []Hit {
- var tks []Hit
- rows, err := db.Query("SELECT date_trunc('$1', created_at), -- or hour, day, week, month, year count(1) FROM hits WHERE service=$2 group by 1", group, s.Id)
- if err != nil {
- panic(err)
- }
- for rows.Next() {
- var tk Hit
- err = rows.Scan(&tk.Id, &tk.Metric, &tk.Value, &tk.CreatedAt)
- if err != nil {
- panic(err)
- }
- tks = append(tks, tk)
- }
- return tks
+func (s *Service) Hits() ([]Hit, error) {
+ var hits []Hit
+ col := dbSession.Collection("hits").Find("service", s.Id)
+ err := col.All(&hits)
+ return hits, err
}
-func (s *Service) TotalHits() int {
- var amount int
- db.QueryRow("SELECT COUNT(id) FROM hits WHERE service=$1;", s.Id).Scan(&amount)
- return amount
+func (s *Service) SelectHitsGroupBy(group string) ([]Hit, error) {
+ var hits []Hit
+ col := dbSession.Collection("hits").Find("service", s.Id)
+ err := col.All(&hits)
+ return hits, err
}
-func (s *Service) Sum() float64 {
+func (s *Service) TotalHits() (uint64, error) {
+ col := dbSession.Collection("hits").Find("service", s.Id)
+ amount, err := col.Count()
+ return amount, err
+}
+
+func (s *Service) Sum() (float64, error) {
var amount float64
- db.QueryRow("SELECT SUM(latency) FROM hits WHERE service=$1;", s.Id).Scan(&amount)
- return amount
+ hits, err := s.Hits()
+ for _, h := range hits {
+ amount += h.Latency
+ }
+ return amount, err
}
diff --git a/html/css/base.css b/html/css/base.css
index 324b6264..b4ed1a64 100644
--- a/html/css/base.css
+++ b/html/css/base.css
@@ -53,6 +53,10 @@ HTML,BODY {
background-color: white !important;
}
+.online_list {
+ font-size: 1.5rem;
+}
+
.footer {
text-decoration: none;
margin-top: 20px;
@@ -86,12 +90,28 @@ HTML,BODY {
overflow: hidden;
}
+.card-body H3 A {
+ color: #424242;
+}
+
@media (max-width: 767px) {
+ HTML,BODY {
+ background-color: #efefef;
+ margin: 0px 0;
+ }
+
.container {
padding: 0px;
}
+ .navbar {
+ margin-left: 0px;
+ margin-top: 0px;
+ width: 100%;
+ margin-bottom: 0;
+ }
+
.card-body {
font-size: 6pt;
}
diff --git a/html/js/setup.js b/html/js/setup.js
new file mode 100644
index 00000000..aed8f9e7
--- /dev/null
+++ b/html/js/setup.js
@@ -0,0 +1,16 @@
+$('select#database_type').on('change', function(){
+ var selected = $('#database_type option:selected').val();
+ if (selected=="sqlite") {
+ $("#db_host").hide();
+ $("#db_password").hide();
+ $("#db_port").hide();
+ $("#db_user").hide();
+ $("#db_database").hide();
+ } else {
+ $("#db_host").show();
+ $("#db_password").show();
+ $("#db_port").show();
+ $("#db_user").show();
+ $("#db_database").show();
+ }
+});
\ No newline at end of file
diff --git a/html/tmpl/dashboard.html b/html/tmpl/dashboard.html
index a64c8298..0dc1167a 100644
--- a/html/tmpl/dashboard.html
+++ b/html/tmpl/dashboard.html
@@ -59,8 +59,10 @@
+
+