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 @@ + + \ No newline at end of file diff --git a/html/tmpl/index.html b/html/tmpl/index.html index 12d052ff..bb563e74 100644 --- a/html/tmpl/index.html +++ b/html/tmpl/index.html @@ -12,14 +12,32 @@ -

{{.Project}}

+

{{.Project}}

+ +
+ + + +
+
{{ range .Services }} -
+
@@ -37,17 +55,17 @@
- {{.Online24Hours}}% + {{.Online24}}% Online last 24 Hours
- {{.AvgResponse}}ms + {{.AvgTime}}ms Average Response
- {{.TotalUptime}}% + {{.AvgUptime}}% Total Uptime
@@ -83,7 +101,7 @@ var chartdata = new Chart(ctx, { data: { datasets: [{ label: 'Response Time (Milliseconds)', - data: {{js .Data}}, + data: {{js .GraphData}}, backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', @@ -134,5 +152,6 @@ var chartdata = new Chart(ctx, { {{ end }} + \ No newline at end of file diff --git a/html/tmpl/login.html b/html/tmpl/login.html index 34b53be6..a99d82b9 100644 --- a/html/tmpl/login.html +++ b/html/tmpl/login.html @@ -16,10 +16,9 @@
-
-
+
@@ -58,6 +57,6 @@
- + \ No newline at end of file diff --git a/html/tmpl/nav.html b/html/tmpl/nav.html index 365488d6..370c0992 100644 --- a/html/tmpl/nav.html +++ b/html/tmpl/nav.html @@ -1,6 +1,6 @@ {{define "nav"}}
+
- + \ No newline at end of file diff --git a/html/tmpl/services.html b/html/tmpl/services.html index 7280f055..4a78146b 100644 --- a/html/tmpl/services.html +++ b/html/tmpl/services.html @@ -108,6 +108,6 @@
- + \ No newline at end of file diff --git a/html/tmpl/settings.html b/html/tmpl/settings.html deleted file mode 100644 index d141a074..00000000 --- a/html/tmpl/settings.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - Statup | Dashboard - - - - -
- -{{template "nav"}} - - -
- -
- -

Statup Settings

- - -
- -
- - - - - \ No newline at end of file diff --git a/html/tmpl/setup.html b/html/tmpl/setup.html index fc7332d2..3c42dafd 100644 --- a/html/tmpl/setup.html +++ b/html/tmpl/setup.html @@ -15,35 +15,36 @@
- +
-
- + +
-
+
- +
-
+
- +
-
+
- +
-
+
-
+
@@ -57,6 +58,11 @@
+
+ + +
+
@@ -91,5 +97,7 @@
+ + \ No newline at end of file diff --git a/html/tmpl/users.html b/html/tmpl/users.html index 66843c6d..95d1488d 100644 --- a/html/tmpl/users.html +++ b/html/tmpl/users.html @@ -40,32 +40,31 @@

Create User

- - -
- -
- -
+ +
+ +
+
-
- -
- -
+
+
+ +
+
-
- -
- -
+
+
+ +
+
-
-
- -
+
+
+
+
- +
+
@@ -74,6 +73,6 @@
- + \ No newline at end of file diff --git a/main.go b/main.go index cf18759f..6a39bbab 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "net/http" "os" plg "plugin" + "strconv" "strings" ) @@ -245,6 +246,11 @@ func main() { mainProcess() } +func StringInt(s string) int64 { + num, _ := strconv.Atoi(s) + return int64(num) +} + func mainProcess() { var err error err = DbConnection(configs.Connection) @@ -253,7 +259,8 @@ func mainProcess() { } core, err = SelectCore() if err != nil { - throw(err) + fmt.Println("Core database was not found, Statup is not setup yet.") + RunHTTPServer() } go CheckServices() if !setupMode { @@ -263,6 +270,7 @@ func mainProcess() { } func throw(err error) { + panic(err) fmt.Println(err) os.Exit(1) } @@ -334,6 +342,7 @@ func LoadConfig() (*Config, error) { return nil, err } err = yaml.Unmarshal(file, &config) + configs = &config return &config, err } diff --git a/main_test.go b/main_test.go index 4164827e..674d97be 100644 --- a/main_test.go +++ b/main_test.go @@ -2,8 +2,6 @@ package main import ( "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" "os" "testing" "time" @@ -12,9 +10,78 @@ import ( func TestInit(t *testing.T) { VERSION = "1.1.1" RenderBoxes() + os.Remove("./statup.db") + Router() } -func TestMakeConfig(t *testing.T) { +func TestMySQLMakeConfig(t *testing.T) { + config := &DbConfig{ + "mysql", + os.Getenv("DB_HOST"), + os.Getenv("DB_USER"), + os.Getenv("DB_PASS"), + os.Getenv("DB_DATABASE"), + 3306, + "Testing MYSQL", + "This is a test of Statup.io!", + "admin", + "admin", + } + err := config.Save() + assert.Nil(t, err) + + _, err = LoadConfig() + assert.Nil(t, err) + assert.Equal(t, "mysql", configs.Connection) + + err = DbConnection(configs.Connection) + assert.Nil(t, err) + +} + +func TestInsertMysqlSample(t *testing.T) { + err := LoadSampleData() + assert.Nil(t, err) +} + +func TestSelectCoreMYQL(t *testing.T) { + var err error + core, err = SelectCore() + assert.Nil(t, err) + assert.Equal(t, "Testing MYSQL", core.Name) + assert.Equal(t, VERSION, core.Version) +} + +func TestSqliteMakeConfig(t *testing.T) { + config := &DbConfig{ + "sqlite", + os.Getenv("DB_HOST"), + os.Getenv("DB_USER"), + os.Getenv("DB_PASS"), + os.Getenv("DB_DATABASE"), + 5432, + "Testing SQLITE", + "This is a test of Statup.io!", + "admin", + "admin", + } + err := config.Save() + assert.Nil(t, err) + + _, err = LoadConfig() + assert.Nil(t, err) + assert.Equal(t, "sqlite", configs.Connection) + + err = DbConnection(configs.Connection) + assert.Nil(t, err) +} + +func TestInsertSqliteSample(t *testing.T) { + err := LoadSampleData() + assert.Nil(t, err) +} + +func TestPostgresMakeConfig(t *testing.T) { config := &DbConfig{ "postgres", os.Getenv("DB_HOST"), @@ -22,40 +89,139 @@ func TestMakeConfig(t *testing.T) { os.Getenv("DB_PASS"), os.Getenv("DB_DATABASE"), 5432, - "Testing", + "Testing POSTGRES", + "This is a test of Statup.io!", "admin", "admin", } err := config.Save() assert.Nil(t, err) + + _, err = LoadConfig() + assert.Nil(t, err) + assert.Equal(t, "postgres", configs.Connection) + + err = DbConnection(configs.Connection) + assert.Nil(t, err) } -func TestSetConfig(t *testing.T) { +func TestInsertPostgresSample(t *testing.T) { + err := LoadSampleData() + assert.Nil(t, err) +} + +func TestSelectCorePostgres(t *testing.T) { + var err error + core, err = SelectCore() + assert.Nil(t, err) + assert.Equal(t, "Testing POSTGRES", core.Name) + assert.Equal(t, VERSION, core.Version) +} + +func TestSelectCore(t *testing.T) { + var err error + core, err = SelectCore() + assert.Nil(t, err) + assert.Equal(t, "Testing POSTGRES", core.Name) + assert.Equal(t, VERSION, core.Version) +} + +func TestUser_Create(t *testing.T) { + user := &User{ + Username: "testuserhere", + Password: "password123", + Email: "info@testuser.com", + } + id, err := user.Create() + assert.Nil(t, err) + assert.NotZero(t, id) +} + +func TestOneService_Check(t *testing.T) { + service, err := SelectService(1) + assert.Nil(t, err) + assert.Equal(t, "Google", service.Name) +} + +func TestService_Create(t *testing.T) { + service := &Service{ + Name: "test service", + Domain: "https://google.com", + ExpectedStatus: 200, + Interval: 1, + Port: 0, + Type: "https", + Method: "GET", + } + id, err := service.Create() + assert.Nil(t, err) + assert.Equal(t, int64(5), id) +} + +func TestService_Check(t *testing.T) { + service, err := SelectService(2) + assert.Nil(t, err) + assert.Equal(t, "Statup.io", service.Name) + out := service.Check() + assert.Equal(t, true, out.Online) +} + +func TestService_Hits(t *testing.T) { + service, err := SelectService(1) + assert.Nil(t, err) + hits, err := service.Hits() + assert.Nil(t, err) + assert.Equal(t, 0, len(hits)) +} + +func TestService_AvgTime(t *testing.T) { + service, err := SelectService(1) + assert.Nil(t, err) + avg := service.AvgUptime() + assert.Nil(t, err) + assert.Equal(t, "100.00", avg) +} + +func TestService_Online24(t *testing.T) { + service, err := SelectService(1) + assert.Nil(t, err) + online := service.Online24() + assert.Nil(t, err) + assert.Equal(t, float32(100), online) +} + +func TestService_GraphData(t *testing.T) { + t.SkipNow() + service, err := SelectService(1) + assert.Nil(t, err) + data := service.GraphData() + assert.Equal(t, "null", data) +} + +func TestBadService_Create(t *testing.T) { + service := &Service{ + Name: "bad service", + Domain: "https://9839f83h72gey2g29278hd2od2d.com", + ExpectedStatus: 200, + Interval: 10, + Port: 0, + Type: "https", + Method: "GET", + } + id, err := service.Create() + assert.Nil(t, err) + assert.Equal(t, int64(6), id) +} + +func TestBadService_Check(t *testing.T) { + service, err := SelectService(4) + assert.Nil(t, err) + assert.Equal(t, "Github Failing Check", service.Name) +} + +func Test(t *testing.T) { var err error configs, err = LoadConfig() assert.Nil(t, err) time.Sleep(2 * time.Second) } - -func TestRun(t *testing.T) { - go mainProcess() - time.Sleep(15 * time.Second) -} - -func TestServiceUrl(t *testing.T) { - req, err := http.NewRequest("GET", "/service/1", nil) - assert.Nil(t, err) - rr := httptest.NewRecorder() - Router().ServeHTTP(rr, req) - - assert.Equal(t, 3305, len(rr.Body.Bytes()), "should be balance") -} - -func Test(t *testing.T) { - req, err := http.NewRequest("GET", "/dashboard", nil) - assert.Nil(t, err) - rr := httptest.NewRecorder() - Router().ServeHTTP(rr, req) - - assert.Equal(t, 2048, len(rr.Body.Bytes()), "should be balance") -} diff --git a/plugin/main.go b/plugin/main.go index 42bd2080..03244d51 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -1,10 +1,10 @@ package plugin import ( - "database/sql" "fmt" "net/http" "time" + "upper.io/db.v3/lib/sqlbuilder" ) // @@ -16,10 +16,10 @@ import ( // var ( - DB *sql.DB + DB sqlbuilder.Database ) -func SetDatabase(database *sql.DB) { +func SetDatabase(database sqlbuilder.Database) { DB = database } @@ -81,7 +81,7 @@ type Service struct { type Failure struct { Id int Issue string - Service int + Service int64 CreatedAt time.Time Ago string } diff --git a/services.go b/services.go index 9e4deae5..091e49b6 100644 --- a/services.go +++ b/services.go @@ -15,51 +15,38 @@ var ( ) type Service struct { - Id int64 - Name string - Domain string - Expected string - ExpectedStatus int - Interval int - Method string - Port int - CreatedAt time.Time - Data string - Online bool - Latency float64 - Online24Hours float32 - AvgResponse string - TotalUptime string - Failures []*Failure + Id int64 `db:"id,omitempty" json:"id"` + Name string `db:"name" json:"name"` + Domain string `db:"domain" json:"domain"` + Expected string `db:"expected" json:"expected"` + ExpectedStatus int `db:"expected_status" json:"expected_status"` + Interval int `db:"check_interval" json:"check_interval"` + Type string `db:"check_type" json:"type"` + Method string `db:"method" json:"method"` + Port int `db:"port" json:"port"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Online bool `json:"online"` + Latency float64 `json:"latency"` + Online24Hours float32 `json:"24_hours_online"` + AvgResponse string `json:"avg_response"` + TotalUptime string `json:"uptime"` + Failures []*Failure `json:"failures"` plugin.Service } -func SelectService(id string) *Service { - for _, s := range services { - if id == strconv.Itoa(int(s.Id)) { - return s - } - } - return nil +func SelectService(id int64) (Service, error) { + var service Service + col := dbSession.Collection("services") + res := col.Find("id", id) + err := res.One(&service) + return service, err } -func SelectAllServices() []*Service { - var tks []*Service - rows, err := db.Query("SELECT * FROM services ORDER BY id ASC") - if err != nil { - panic(err) - } - for rows.Next() { - var tk Service - err = rows.Scan(&tk.Id, &tk.Name, &tk.Domain, &tk.Method, &tk.Port, &tk.Expected, &tk.ExpectedStatus, &tk.Interval, &tk.CreatedAt) - if err != nil { - panic(err) - } - tk.Failures = tk.SelectAllFailures() - tk.FormatData() - tks = append(tks, &tk) - } - return tks +func SelectAllServices() ([]*Service, error) { + var services []*Service + col := dbSession.Collection("services").Find() + err := col.All(&services) + return services, err } func (s *Service) FormatData() *Service { @@ -71,16 +58,20 @@ func (s *Service) FormatData() *Service { } func (s *Service) AvgTime() float64 { - total := s.TotalHits() - sum := s.Sum() + total, _ := s.TotalHits() + if total == 0 { + return float64(0) + } + sum, _ := s.Sum() avg := sum / float64(total) * 100 - s.AvgResponse = fmt.Sprintf("%0.0f", avg*10) - return avg + amount := fmt.Sprintf("%0.0f", avg*10) + val, _ := strconv.ParseFloat(amount, 10) + return val } func (s *Service) Online24() float32 { - total := s.TotalHits() - failed := s.TotalFailures24Hours() + total, _ := s.TotalHits() + failed, _ := s.TotalFailures24Hours() if failed == 0 { s.Online24Hours = 100.00 return s.Online24Hours @@ -105,12 +96,13 @@ type GraphJson struct { } func (s *Service) GraphData() string { - var d []*GraphJson - for _, h := range s.Hits() { + var d []GraphJson + hits, _ := s.Hits() + for _, h := range hits { val := h.CreatedAt - o := &GraphJson{ + o := GraphJson{ X: val.String(), - Y: h.Value * 1000, + Y: h.Latency * 1000, } d = append(d, o) } @@ -120,8 +112,8 @@ func (s *Service) GraphData() string { } func (s *Service) AvgUptime() string { - failed := s.TotalFailures() - total := s.TotalHits() + failed, _ := s.TotalFailures() + total, _ := s.TotalHits() if failed == 0 { s.TotalUptime = "100.00" return s.TotalUptime @@ -139,27 +131,26 @@ func (s *Service) AvgUptime() string { return s.TotalUptime } -func (u *Service) Delete() { - stmt, err := db.Prepare("DELETE FROM services WHERE id=$1") - if err != nil { - panic(err) - } - stmt.Exec(u.Id) +func (u *Service) Delete() error { + col := dbSession.Collection("services") + res := col.Find("id", u.Id) + err := res.Delete() + return err } func (u *Service) Update() { } -func (u *Service) Create() int { - var lastInsertId int - err := db.QueryRow("INSERT INTO services(name, domain, method, port, expected, expected_status, interval, created_at) VALUES($1,$2,$3,$4,$5,$6,$7,NOW()) returning id;", u.Name, u.Domain, u.Method, u.Port, u.Expected, u.ExpectedStatus, u.Interval).Scan(&lastInsertId) - if err != nil { - panic(err) +func (u *Service) Create() (int64, error) { + u.CreatedAt = time.Now() + col := dbSession.Collection("services") + uuid, err := col.Insert(u) + services, _ = SelectAllServices() + if uuid == nil { + return 0, err } - services = SelectAllServices() - go u.CheckQueue() - return lastInsertId + return uuid.(int64), err } func CountOnline() int { @@ -174,17 +165,13 @@ func CountOnline() int { 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) } diff --git a/setup.go b/setup.go index c17343dd..bb7ec5d3 100644 --- a/setup.go +++ b/setup.go @@ -2,6 +2,7 @@ package main import ( "github.com/go-yaml/yaml" + "github.com/hunterlong/statup/plugin" "net/http" "os" "strconv" @@ -9,15 +10,16 @@ import ( ) 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:"-"` - Username string `yaml:"-"` - Password string `yaml:"-"` + 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:"-"` + Username string `yaml:"-"` + Password string `yaml:"-"` } func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { @@ -31,6 +33,8 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { 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") config := &DbConfig{ dbConn, @@ -40,20 +44,32 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { dbDatabase, dbPort, project, + description, username, password, } - err := config.Save() if err != nil { - panic(err) + throw(err) + } + + configs, err = LoadConfig() + if err != nil { + throw(err) + } + + err = DbConnection(configs.Connection) + if err != nil { + throw(err) + } + + if sample == "on" { + LoadSampleData() } http.Redirect(w, r, "/", http.StatusSeeOther) time.Sleep(2 * time.Second) - mainProcess() - } func (c *DbConfig) Save() error { @@ -79,6 +95,21 @@ func (c *DbConfig) Save() error { } DropDatabase() CreateDatabase() - db.QueryRow("INSERT INTO core (name, config, api_key, api_secret, version) VALUES($1,$2,$3,$4,$5);", c.Project, "config.yml", NewSHA1Hash(5), NewSHA1Hash(10), VERSION).Scan() + + newCore := Core{ + c.Project, + c.Description, + "config.yml", + NewSHA1Hash(5), + NewSHA1Hash(10), + VERSION, + []plugin.Info{}, + []PluginJSON{}, + []PluginSelect{}, + } + + col := dbSession.Collection("core") + _, err = col.Insert(newCore) + return err } diff --git a/sql/down.sql b/sql/down.sql index ec67df5b..5a508ab9 100644 --- a/sql/down.sql +++ b/sql/down.sql @@ -1,5 +1,5 @@ DROP table core; DROP table hits; DROP table failures; -DROP table services; -DROP table users; \ No newline at end of file +DROP table users; +DROP table services; \ No newline at end of file diff --git a/sql/mysql_up.sql b/sql/mysql_up.sql new file mode 100644 index 00000000..6a218dc9 --- /dev/null +++ b/sql/mysql_up.sql @@ -0,0 +1,47 @@ +CREATE TABLE core ( + name VARCHAR(50), + description text, + config VARCHAR(50), + api_key VARCHAR(50), + api_secret VARCHAR(50), + version VARCHAR(50) +); +CREATE TABLE users ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50), + password text, + email text, + api_key VARCHAR(50), + api_secret VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX (id) +); +CREATE TABLE services ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50), + domain text, + check_type text, + method VARCHAR(50), + port INT(6), + expected text, + expected_status INT(6), + check_interval int(11), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX (id) +); +CREATE TABLE hits ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + service INTEGER NOT NULL, + latency float, + created_at TIMESTAMP, + INDEX (id, service), + FOREIGN KEY (service) REFERENCES services(id) +); +CREATE TABLE failures ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + issue text, + service INTEGER NOT NULL, + created_at TIMESTAMP, + INDEX (id, service), + FOREIGN KEY (service) REFERENCES services(id) +); \ No newline at end of file diff --git a/sql/postgres_up.sql b/sql/postgres_up.sql new file mode 100644 index 00000000..c1a99663 --- /dev/null +++ b/sql/postgres_up.sql @@ -0,0 +1,48 @@ +CREATE TABLE core ( + name text, + description text, + config text, + api_key text, + api_secret text, + version text +); + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username text, + password text, + email text, + api_key text, + api_secret text, + created_at TIMESTAMP +); + +CREATE TABLE services ( + id SERIAL PRIMARY KEY, + name text, + domain text, + check_type text, + method text, + port integer, + expected text, + expected_status integer, + check_interval integer, + created_at TIMESTAMP +); + +CREATE TABLE hits ( + id SERIAL PRIMARY KEY, + service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE, + latency float, + created_at TIMESTAMP WITHOUT TIME zone +); + +CREATE TABLE failures ( + id SERIAL PRIMARY KEY, + issue text, + service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE, + created_at TIMESTAMP WITHOUT TIME zone +); + +CREATE INDEX idx_hits ON hits(service); +CREATE INDEX idx_failures ON failures(service); \ No newline at end of file diff --git a/sql/sqlite_up.sql b/sql/sqlite_up.sql new file mode 100644 index 00000000..1bebd63d --- /dev/null +++ b/sql/sqlite_up.sql @@ -0,0 +1,48 @@ +CREATE TABLE core ( + name text, + description text, + config text, + api_key text, + api_secret text, + version text +); + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username text, + password text, + email text, + api_key text, + api_secret text, + created_at TIMESTAMP +); + +CREATE TABLE services ( + id SERIAL PRIMARY KEY, + name text, + domain text, + check_type text, + method text, + port integer, + expected text, + expected_status integer, + check_interval integer, + created_at TIMESTAMP +); + +CREATE TABLE hits ( + id SERIAL PRIMARY KEY, + service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE, + latency float, + created_at TIMESTAMP +); + +CREATE TABLE failures ( + id SERIAL PRIMARY KEY, + issue text, + service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE, + created_at TIMESTAMP +); + +CREATE INDEX idx_hits ON hits(service); +CREATE INDEX idx_failures ON failures(service); \ No newline at end of file diff --git a/sql/up.sql b/sql/up.sql deleted file mode 100644 index 3e3c01ff..00000000 --- a/sql/up.sql +++ /dev/null @@ -1,60 +0,0 @@ -CREATE TABLE core ( - name text, - config text, - api_key text, - api_secret text, - version text, -); - -CREATE TABLE users ( - id SERIAL PRIMARY KEY, - username text, - password text, - key text, - secret text, - created_at TIMESTAMP WITHOUT TIME zone -); - -CREATE TABLE services ( - id SERIAL PRIMARY KEY, - name text, - domain text, - method text, - port integer, - expected text, - expected_status integer, - interval integer, - created_at TIMESTAMP WITHOUT TIME zone -); - -CREATE TABLE hits ( - id SERIAL PRIMARY KEY, - service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE, - latency float, - created_at TIMESTAMP WITHOUT TIME zone -); - -CREATE TABLE failures ( - id SERIAL PRIMARY KEY, - issue text, - service INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE ON UPDATE CASCADE, - created_at TIMESTAMP WITHOUT TIME zone -); - -CREATE INDEX idx_hits ON hits(service); -CREATE INDEX idx_failures ON failures(service); - -INSERT INTO users (id, username, password, created_at) VALUES (1, 'admin', '$2a$14$sBO5VDKiGPNUa3IUSMRX.OJNIbw/VM5dXOzTjlsjvG6qA987Lfzga', NOW()); - -INSERT INTO services (id, name, domain, method, port, expected, expected_status, interval, created_at) VALUES (1, 'Statup Demo', 'https://demo.statup.io', 'https', 0, '', 200, 5, NOW()); - -INSERT INTO services (id, name, domain, method, port, expected, expected_status, interval, created_at) VALUES (2, 'Github', 'https://github.com', 'https', 0, '', 200, 10, NOW()); - -INSERT INTO services (id, name, domain, method, port, expected, expected_status, interval, created_at) VALUES (3, 'Santa Monica', 'https://www.santamonica.com', 'https', 0, '', 200, 30, NOW()); - -INSERT INTO services (id, name, domain, method, port, expected, expected_status, interval, created_at) VALUES (4, 'Example JSON', 'https://jsonplaceholder.typicode.com/posts/42', 'https', 0, 'userId', 200, 5, NOW()); - -INSERT INTO services (id, name, domain, method, port, expected, expected_status, interval, created_at) VALUES (5, 'Token Balance API', 'https://api.tokenbalance.com/token/0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0/0x5c98dc37c0b3ef75476eb6b8ef0d564f7c6af6ae', 'https', 0, 'broken', 200, 8, NOW()); - -INSERT INTO services (id, name, domain, method, port, expected, expected_status, interval, created_at) VALUES (6, 'Example JSON 2', 'https://jsonplaceholder.typicode.com/posts/42', 'https', 0, 'commodi ullam sint et excepturi error explicabo praesentium voluptas', 200, 13, NOW()); - diff --git a/users.go b/users.go index 03c579ac..7a4de57f 100644 --- a/users.go +++ b/users.go @@ -2,58 +2,62 @@ package main import ( "golang.org/x/crypto/bcrypt" + "time" ) type User struct { - Id int64 - Username string - Password string - Email string + Id int64 `db:"id,omitempty" json:"id"` + Username string `db:"username" json:"username"` + Password string `db:"password" json:"-"` + Email string `db:"email" json:"-"` + ApiKey string `db:"api_key" json:"api_key"` + ApiSecret string `db:"api_secret" json:"-"` + CreatedAt time.Time `db:"created_at" json:"created_at"` } -func SelectUser(username string) User { +func SelectUser(id int64) (*User, error) { var user User - rows, err := db.Query("SELECT id, username, password FROM users WHERE username=$1", username) - if err != nil { - panic(err) - } - for rows.Next() { - err = rows.Scan(&user.Id, &user.Username, &user.Password) - if err != nil { - panic(err) - } - } - return user + col := dbSession.Collection("users") + res := col.Find("id", id) + err := res.One(&user) + return &user, err } -func (u *User) Create() int { +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) Create() (int64, error) { + u.CreatedAt = time.Now() password := HashPassword(u.Password) - var lastInsertId int - db.QueryRow("INSERT INTO users(username,password,created_at) VALUES($1,$2,NOW()) returning id;", u.Username, password).Scan(&lastInsertId) - return lastInsertId + u.Password = password + u.ApiKey = NewSHA1Hash(5) + u.ApiSecret = NewSHA1Hash(10) + col := dbSession.Collection("users") + uuid, err := col.Insert(u) + if uuid == nil { + return 0, err + } + return uuid.(int64), err } -func SelectAllUsers() []User { +func SelectAllUsers() ([]User, error) { var users []User - rows, err := db.Query("SELECT id, username, password FROM users ORDER BY id ASC") - if err != nil { - panic(err) - } - for rows.Next() { - var user User - err = rows.Scan(&user.Id, &user.Username, &user.Password) - if err != nil { - panic(err) - } - users = append(users, user) - } - return users + col := dbSession.Collection("users").Find() + err := col.All(&users) + return users, err } -func AuthUser(username, password string) (User, bool) { - var user User +func AuthUser(username, password string) (*User, bool) { var auth bool - user = SelectUser(username) + user, err := SelectUsername(username) + if err != nil { + return nil, false + } if CheckHash(password, user.Password) { auth = true } diff --git a/web.go b/web.go index a2aa0bf4..5e68b052 100644 --- a/web.go +++ b/web.go @@ -15,28 +15,40 @@ var ( session *sessions.CookieStore ) +const ( + cookieKey = "apizer_auth" +) + func Router() *mux.Router { r := mux.NewRouter() r.Handle("/", http.HandlerFunc(IndexHandler)) r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox()))) r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox()))) - r.Handle("/setup", http.HandlerFunc(SetupHandler)) - r.Handle("/setup/save", http.HandlerFunc(ProcessSetupHandler)) - r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)) - r.Handle("/login", http.HandlerFunc(LoginHandler)) + 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)) + r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET") r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST") r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)) 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("/users", http.HandlerFunc(UsersHandler)) + r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler)) + r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET") r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST") r.Handle("/settings", http.HandlerFunc(PluginsHandler)) 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/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)) return r } @@ -64,20 +76,18 @@ func RunHTTPServer() { } func LogoutHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") + session, _ := store.Get(r, cookieKey) session.Values["authenticated"] = false session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } func LoginHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") + session, _ := store.Get(r, cookieKey) r.ParseForm() username := r.PostForm.Get("username") password := r.PostForm.Get("password") - user, auth := AuthUser(username, password) - fmt.Println(user) - fmt.Println(auth) + _, auth := AuthUser(username, password) if auth { session.Values["authenticated"] = true session.Save(r, w) @@ -89,24 +99,8 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { } } -//func AuthenticateHandler(w http.ResponseWriter, r *http.Request) { -// r.ParseForm() -// key := r.PostForm.Get("key") -// secret := r.PostForm.Get("secret") -// token := SelectToken(key, secret) -// if token.Id != 0 { -// go token.Hit(r) -// w.WriteHeader(200) -// w.Header().Set("Content-Type", "plain/text") -// fmt.Fprintln(w, token.Id) -// } else { -// w.WriteHeader(502) -// w.Header().Set("Content-Type", "plain/text") -// fmt.Fprintln(w, "bad") -// } -//} - func CreateUserHandler(w http.ResponseWriter, r *http.Request) { + fmt.Println("creating user") r.ParseForm() username := r.PostForm.Get("username") password := r.PostForm.Get("password") @@ -114,11 +108,15 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) { Username: username, Password: password, } - user.Create() + _, err := user.Create() + if err != nil { + panic(err) + } http.Redirect(w, r, "/users", http.StatusSeeOther) } func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { + fmt.Println("service adding") r.ParseForm() name := r.PostForm.Get("name") domain := r.PostForm.Get("domain") @@ -127,7 +125,9 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { status, _ := strconv.Atoi(r.PostForm.Get("expected_status")) interval, _ := strconv.Atoi(r.PostForm.Get("interval")) - service := &Service{ + fmt.Println(r.PostForm) + + service := Service{ Name: name, Domain: domain, Method: method, @@ -138,7 +138,10 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { fmt.Println(service) - service.Create() + _, err := service.Create() + if err != nil { + go service.CheckQueue() + } http.Redirect(w, r, "/services", http.StatusSeeOther) } @@ -168,29 +171,30 @@ type dashboard struct { Core *Core CountOnline int CountServices int - Count24Failures int + Count24Failures uint64 } func DashboardHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") + session, _ := store.Get(r, cookieKey) if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { tmpl := Parse("login.html") tmpl.Execute(w, nil) } else { tmpl := Parse("dashboard.html") - out := dashboard{services, core, CountOnline(), len(services), CountFailures()} + fails, _ := CountFailures() + out := dashboard{services, core, CountOnline(), len(services), fails} tmpl.Execute(w, out) } } type serviceHandler struct { - Service *Service + Service Service Auth bool } func ServicesHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") + session, _ := store.Get(r, cookieKey) if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { http.Redirect(w, r, "/", http.StatusSeeOther) return @@ -200,21 +204,21 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) { } func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") + session, _ := store.Get(r, cookieKey) if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { http.Redirect(w, r, "/", http.StatusSeeOther) return } vars := mux.Vars(r) - service := SelectService(vars["id"]) + service, _ := SelectService(StringInt(vars["id"])) service.Delete() - services = SelectAllServices() + services, _ = SelectAllServices() http.Redirect(w, r, "/services", http.StatusSeeOther) } func IsAuthenticated(r *http.Request) bool { - session, _ := store.Get(r, "apizer_auth") + session, _ := store.Get(r, cookieKey) if session.Values["authenticated"] == nil { return false } @@ -298,15 +302,31 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { //service := SelectService(vars["id"]) } +func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + service, _ := SelectService(StringInt(vars["id"])) + + var badge []byte + if service.Online { + badge = []byte(``+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 ServicesViewHandler(w http.ResponseWriter, r *http.Request) { auth := IsAuthenticated(r) vars := mux.Vars(r) - service := SelectService(vars["id"]) - + service, _ := SelectService(StringInt(vars["id"])) tmpl := Parse("service.html") - serve := &serviceHandler{service, auth} - tmpl.Execute(w, serve) } @@ -353,28 +373,13 @@ func ParsePlugins(file string) *template.Template { } func UsersHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") + fmt.Println("viewing user") + session, _ := store.Get(r, cookieKey) if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { http.Redirect(w, r, "/", http.StatusSeeOther) return } tmpl := Parse("users.html") - tmpl.Execute(w, SelectAllUsers()) -} - -func PermissionsHandler(w http.ResponseWriter, r *http.Request) { - session, _ := store.Get(r, "apizer_auth") - if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { - http.Redirect(w, r, "/", http.StatusSeeOther) - return - } - permsFile, err := tmplBox.String("permissions.html") - if err != nil { - panic(err) - } - permsTmpl, err := template.New("message").Parse(permsFile) - if err != nil { - panic(err) - } - permsTmpl.Execute(w, SelectAllUsers()) + users, _ := SelectAllUsers() + tmpl.Execute(w, users) } diff --git a/web_test.go b/web_test.go new file mode 100644 index 00000000..c88b917a --- /dev/null +++ b/web_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "bytes" + "encoding/json" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +func TestServiceUrl(t *testing.T) { + req, err := http.NewRequest("GET", "/service/1", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + assert.Equal(t, 3355, len(rr.Body.Bytes()), "should be balance") +} + +func TestApiAllServiceUrl(t *testing.T) { + req, err := http.NewRequest("GET", "/api/services", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + var data []Service + json.Unmarshal(rr.Body.Bytes(), &data) + assert.Equal(t, "Google", data[0].Name, "should be balance") +} + +func TestApiServiceUrl(t *testing.T) { + req, err := http.NewRequest("GET", "/api/services/1", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + var data Service + json.Unmarshal(rr.Body.Bytes(), &data) + assert.Equal(t, "Google", data.Name, "should be balance") +} + +func TestApiServiceUpdateUrl(t *testing.T) { + payload := []byte(`{"name":"test product - updated name","price":11.22}`) + req, err := http.NewRequest("POST", "/api/services/1", bytes.NewBuffer(payload)) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + var data Service + json.Unmarshal(rr.Body.Bytes(), &data) + assert.Equal(t, "Google", data.Name, "should be balance") +} + +func TestApiUserUrl(t *testing.T) { + req, err := http.NewRequest("GET", "/api/users/1", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + var data User + json.Unmarshal(rr.Body.Bytes(), &data) + assert.Equal(t, "admin", data.Username, "should be balance") +} + +func TestApiAllUsersUrl(t *testing.T) { + req, err := http.NewRequest("GET", "/api/users", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + var data []User + json.Unmarshal(rr.Body.Bytes(), &data) + assert.Equal(t, "admin", data[0].Username, "should be balance") +} + +func TestDashboardHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/dashboard", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + assert.Equal(t, 2095, len(rr.Body.Bytes()), "should be balance") +} + +func TestLoginHandler(t *testing.T) { + form := url.Values{} + form.Add("username", "admin") + form.Add("password", "admin") + req, err := http.NewRequest("POST", "/dashboard", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + assert.Nil(t, err) + rr := httptest.NewRecorder() + Router().ServeHTTP(rr, req) + assert.Equal(t, 303, rr.Result().StatusCode, "should be balance") +}