pull/10/head
Hunter Long 2018-06-14 21:30:10 -07:00
parent e265765c75
commit 9a237defe7
32 changed files with 1056 additions and 501 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
.idea .idea
rice-box.go rice-box.go
config.yml config.yml
statup.db
plugins/*.so

View File

@ -12,6 +12,9 @@ sudo: required
services: services:
- docker - docker
- postgresql
- mysql
- mongodb
env: env:
- VERSION=0.12 - VERSION=0.12
@ -37,14 +40,12 @@ deploy:
- "build/statup-windows-x32.exe" - "build/statup-windows-x32.exe"
skip_cleanup: true skip_cleanup: true
services:
- postgresql
notifications: notifications:
email: false email: false
before_install: before_install:
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then travis_wait 30 docker pull karalabe/xgo-latest; fi - if [[ "$TRAVIS_BRANCH" == "master" ]]; then travis_wait 30 docker pull karalabe/xgo-latest; fi
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
before_script: before_script:
- psql -c 'create database travis_ci_test;' -U postgres - psql -c 'create database travis_ci_test;' -U postgres

44
api.go Normal file
View File

@ -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)
}

View File

@ -9,7 +9,7 @@ import (
) )
func CheckServices() { func CheckServices() {
services = SelectAllServices() services, _ = SelectAllServices()
for _, v := range services { for _, v := range services {
obj := v obj := v
go obj.CheckQueue() go obj.CheckQueue()
@ -18,12 +18,15 @@ func CheckServices() {
func (s *Service) CheckQueue() { func (s *Service) CheckQueue() {
s.Check() s.Check()
if s.Interval < 1 {
s.Interval = 1
}
fmt.Printf(" Service: %v | Online: %v | Latency: %v\n", s.Name, s.Online, s.Latency) fmt.Printf(" Service: %v | Online: %v | Latency: %v\n", s.Name, s.Online, s.Latency)
time.Sleep(time.Duration(s.Interval) * time.Second) time.Sleep(time.Duration(s.Interval) * time.Second)
s.CheckQueue() s.CheckQueue()
} }
func (s *Service) Check() { func (s *Service) Check() *Service {
t1 := time.Now() t1 := time.Now()
client := http.Client{ client := http.Client{
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
@ -33,7 +36,7 @@ func (s *Service) Check() {
s.Latency = t2.Sub(t1).Seconds() s.Latency = t2.Sub(t1).Seconds()
if err != nil { if err != nil {
s.Failure(fmt.Sprintf("HTTP Error %v", err)) s.Failure(fmt.Sprintf("HTTP Error %v", err))
return return s
} }
defer response.Body.Close() defer response.Body.Close()
if s.Expected != "" { if s.Expected != "" {
@ -41,23 +44,40 @@ func (s *Service) Check() {
match, _ := regexp.MatchString(s.Expected, string(contents)) match, _ := regexp.MatchString(s.Expected, string(contents))
if !match { if !match {
s.Failure(fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected)) s.Failure(fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
return return s
} }
} }
if s.ExpectedStatus != response.StatusCode { if s.ExpectedStatus != response.StatusCode {
s.Failure(fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus)) s.Failure(fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus))
return return s
} }
s.Online = true s.Online = true
s.Record(response) s.Record(response)
return s
}
type HitData struct {
Latency float64
} }
func (s *Service) Record(response *http.Response) { 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) OnSuccess(s)
} }
type FailureData struct {
Issue string
}
func (s *Service) Failure(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) OnFailure(s)
} }

22
core.go
View File

@ -6,29 +6,23 @@ import (
) )
type Core struct { type Core struct {
Name string Name string `db:"name"`
Config string Description string `db:"description"`
Key string Config string `db:"config"`
Secret string ApiKey string `db:"api_key"`
Version string ApiSecret string `db:"api_secret"`
Version string `db:"version"`
Plugins []plugin.Info Plugins []plugin.Info
Repos []PluginJSON Repos []PluginJSON
PluginFields []PluginSelect PluginFields []PluginSelect
} }
func SelectCore() (*Core, error) { func SelectCore() (*Core, error) {
var core Core var core Core
rows, err := db.Query("SELECT * FROM core") err := dbSession.Collection("core").Find().One(&core)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for rows.Next() { store = sessions.NewCookieStore([]byte(core.ApiSecret))
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))
return &core, err return &core, err
} }

View File

@ -1,38 +1,59 @@
package main package main
import ( import (
"database/sql"
"fmt" "fmt"
"github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/plugin"
_ "github.com/lib/pq" "strings"
_ "github.com/mattn/go-sqlite3" "upper.io/db.v3/lib/sqlbuilder"
_ "github.com/go-sql-driver/mysql" "upper.io/db.v3/mysql"
"math/rand" "upper.io/db.v3/postgresql"
"time" "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 { func DbConnection(dbType string) error {
var err error var err error
var dbInfo string if dbType == "sqlite" {
if dbType=="sqlite3" { sqliteSettings = sqlite.ConnectionURL{
dbInfo = "./statup.db" Database: "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) 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 { } 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) //dbSession.SetLogging(true)
if err != nil { dbServer = dbType
return err plugin.SetDatabase(dbSession)
}
//stmt, err := db.Prepare("CREATE database statup;")
//if err != nil {
// panic(err)
//}
//stmt.Exec()
plugin.SetDatabase(db)
return err return err
} }
@ -45,33 +66,104 @@ func UpgradeDatabase() {
} }
fmt.Println("Upgrading Database...") fmt.Println("Upgrading Database...")
upgrade, _ := sqlBox.String("upgrade.sql") 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() { func DropDatabase() {
fmt.Println("Dropping Tables...") fmt.Println("Dropping Tables...")
down, _ := sqlBox.String("down.sql") 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() { func CreateDatabase() {
fmt.Println("Creating Tables...") fmt.Println("Creating Tables...")
VERSION = "1.1.1" VERSION = "1.1.1"
up, _ := sqlBox.String("up.sql") sql := "postgres_up.sql"
db.QueryRow(up).Scan() 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() //secret := NewSHA1Hash()
//db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan() //db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan()
fmt.Println("Database Created") fmt.Println("Database Created")
//SampleData() //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++
}
}

View File

@ -1,54 +1,53 @@
package main package main
import ( import (
"github.com/ararog/timeago"
"time" "time"
) )
type Failure struct { type Failure struct {
Id int Id int `db:"id,omitempty"`
Issue string Issue string `db:"issue"`
Service int Service int64 `db:"service"`
CreatedAt time.Time CreatedAt time.Time `db:"created_at"`
Ago string Ago string
} }
func (s *Service) SelectAllFailures() []*Failure { func (s *Service) CreateFailure(data FailureData) (int64, error) {
var tks []*Failure fail := &Failure{
rows, err := db.Query("SELECT * FROM failures WHERE service=$1 ORDER BY id DESC LIMIT 10", s.Id) Issue: data.Issue,
if err != nil { Service: s.Id,
panic(err) CreatedAt: time.Now(),
} }
for rows.Next() { s.Failures = append(s.Failures, fail)
var tk Failure col := dbSession.Collection("failures")
err = rows.Scan(&tk.Id, &tk.Issue, &tk.Service, &tk.CreatedAt) uuid, err := col.Insert(fail)
if err != nil { if uuid == nil {
panic(err) return 0, err
}
tk.Ago, _ = timeago.TimeAgoWithTime(time.Now(), tk.CreatedAt)
tks = append(tks, &tk)
} }
return tks return uuid.(int64), err
} }
func CountFailures() int { func (s *Service) SelectAllFailures() ([]*Failure, error) {
var amount int var fails []*Failure
db.QueryRow("SELECT COUNT(id) FROM failures;").Scan(&amount) col := dbSession.Collection("failures").Find("session", s.Id)
return amount err := col.All(&fails)
return fails, err
} }
func (s *Service) TotalFailures() int { func CountFailures() (uint64, error) {
var amount int col := dbSession.Collection("failures").Find()
db.QueryRow("SELECT COUNT(id) FROM failures WHERE service=$1;", s.Id).Scan(&amount) amount, err := col.Count()
return amount return amount, err
} }
func (s *Service) TotalFailures24Hours() int { func (s *Service) TotalFailures() (uint64, error) {
var amount int col := dbSession.Collection("failures").Find("service", s.Id)
t := time.Now() amount, err := col.Count()
x := t.AddDate(0, 0, -1) return amount, err
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) TotalFailures24Hours() (uint64, error) {
col := dbSession.Collection("failures").Find("service", s.Id)
amount, err := col.Count()
return amount, err
} }

75
hits.go
View File

@ -3,54 +3,51 @@ package main
import "time" import "time"
type Hit struct { type Hit struct {
Id int Id int `db:"id,omitempty"`
Metric int Service int64 `db:"service"`
Value float64 Latency float64 `db:"latency"`
CreatedAt time.Time CreatedAt time.Time `db:"created_at"`
} }
func (s *Service) Hits() []Hit { func (s *Service) CreateHit(d HitData) (int64, error) {
var tks []Hit h := Hit{
rows, err := db.Query("SELECT * FROM hits WHERE service=$1 ORDER BY id DESC LIMIT 256", s.Id) Service: s.Id,
if err != nil { Latency: d.Latency,
panic(err) CreatedAt: time.Now(),
} }
for rows.Next() { col := dbSession.Collection("hits")
var tk Hit uuid, err := col.Insert(h)
err = rows.Scan(&tk.Id, &tk.Metric, &tk.Value, &tk.CreatedAt) if uuid == nil {
if err != nil { return 0, err
panic(err)
}
tks = append(tks, tk)
} }
return tks return uuid.(int64), err
} }
func (s *Service) SelectHitsGroupBy(group string) []Hit { func (s *Service) Hits() ([]Hit, error) {
var tks []Hit var hits []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) col := dbSession.Collection("hits").Find("service", s.Id)
if err != nil { err := col.All(&hits)
panic(err) return hits, 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) TotalHits() int { func (s *Service) SelectHitsGroupBy(group string) ([]Hit, error) {
var amount int var hits []Hit
db.QueryRow("SELECT COUNT(id) FROM hits WHERE service=$1;", s.Id).Scan(&amount) col := dbSession.Collection("hits").Find("service", s.Id)
return amount 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 var amount float64
db.QueryRow("SELECT SUM(latency) FROM hits WHERE service=$1;", s.Id).Scan(&amount) hits, err := s.Hits()
return amount for _, h := range hits {
amount += h.Latency
}
return amount, err
} }

View File

@ -53,6 +53,10 @@ HTML,BODY {
background-color: white !important; background-color: white !important;
} }
.online_list {
font-size: 1.5rem;
}
.footer { .footer {
text-decoration: none; text-decoration: none;
margin-top: 20px; margin-top: 20px;
@ -86,12 +90,28 @@ HTML,BODY {
overflow: hidden; overflow: hidden;
} }
.card-body H3 A {
color: #424242;
}
@media (max-width: 767px) { @media (max-width: 767px) {
HTML,BODY {
background-color: #efefef;
margin: 0px 0;
}
.container { .container {
padding: 0px; padding: 0px;
} }
.navbar {
margin-left: 0px;
margin-top: 0px;
width: 100%;
margin-bottom: 0;
}
.card-body { .card-body {
font-size: 6pt; font-size: 6pt;
} }

16
html/js/setup.js Normal file
View File

@ -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();
}
});

View File

@ -59,8 +59,10 @@
</div> </div>
</div> </div>
</div>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -12,14 +12,32 @@
</head> </head>
<body> <body>
<h1 class="text-center mb-4">{{.Project}}</h1> <h1 class="text-center mb-4 mt-sm-3">{{.Project}}</h1>
<div class="container"> <div class="container">
<div class="col-12 mb-5">
<div class="list-group online_list">
{{ range .Services }}
<a href="#" class="list-group-item list-group-item-action {{if .Online}}{{ end }}">
{{ .Name }}
{{if .Online}}
<span class="badge online_badge float-right">ONLINE</span>
{{ else }}
<span class="badge offline_badge float-right">OFFLINE</span>
{{end}}
</a>
{{ end }}
</div>
</div>
<div class="col-12"> <div class="col-12">
{{ range .Services }} {{ range .Services }}
<div class="col-12 mb-4"> <div class="mb-4">
<div class="card"> <div class="card">
<div class="card-body{{if .Online}}{{else}} offline_bg{{end}}"> <div class="card-body{{if .Online}}{{else}} offline_bg{{end}}">
@ -37,17 +55,17 @@
<div class="row stats_area mt-5 mb-5"> <div class="row stats_area mt-5 mb-5">
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Online24Hours}}%</span> <span class="lg_number">{{.Online24}}%</span>
Online last 24 Hours Online last 24 Hours
</div> </div>
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.AvgResponse}}ms</span> <span class="lg_number">{{.AvgTime}}ms</span>
Average Response Average Response
</div> </div>
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.TotalUptime}}%</span> <span class="lg_number">{{.AvgUptime}}%</span>
Total Uptime Total Uptime
</div> </div>
</div> </div>
@ -83,7 +101,7 @@ var chartdata = new Chart(ctx, {
data: { data: {
datasets: [{ datasets: [{
label: 'Response Time (Milliseconds)', label: 'Response Time (Milliseconds)',
data: {{js .Data}}, data: {{js .GraphData}},
backgroundColor: [ backgroundColor: [
'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)', 'rgba(54, 162, 235, 0.2)',
@ -134,5 +152,6 @@ var chartdata = new Chart(ctx, {
{{ end }} {{ end }}
</script> </script>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -16,10 +16,9 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<form action="/login" method="POST"> <form action="/dashboard" method="POST">
<div class="form-group row"> <div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label> <label for="inputEmail3" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10"> <div class="col-sm-10">
@ -58,6 +57,6 @@
</div> </div>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
{{define "nav"}} {{define "nav"}}
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Statup</a> <a class="navbar-brand" href="/">Statup</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>

View File

@ -34,17 +34,17 @@
<div class="row stats_area mt-5 mb-5"> <div class="row stats_area mt-5 mb-5">
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Service.Online24Hours}}%</span> <span class="lg_number">{{.Service.Online24}}%</span>
Online last 24 Hours Online last 24 Hours
</div> </div>
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Service.AvgResponse}}ms</span> <span class="lg_number">{{.Service.AvgTime}}ms</span>
Average Response Average Response
</div> </div>
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Service.TotalUptime}}%</span> <span class="lg_number">{{.Service.AvgUptime}}%</span>
Total Uptime Total Uptime
</div> </div>
</div> </div>
@ -118,6 +118,7 @@
</div> </div>
</div>
<script> <script>
@ -128,7 +129,7 @@
data: { data: {
datasets: [{ datasets: [{
label: 'Response Time (Milliseconds)', label: 'Response Time (Milliseconds)',
data: {{js .Service.Data}}, data: {{js .Service.GraphData}},
backgroundColor: [ backgroundColor: [
'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)', 'rgba(54, 162, 235, 0.2)',
@ -179,6 +180,6 @@
</script> </script>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -108,6 +108,6 @@
</div> </div>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -1,34 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css">
<title>Statup | Dashboard</title>
</head>
<body>
<div class="container">
{{template "nav"}}
<div class="row">
<div class="col-12">
<h3>Statup Settings</h3>
</div>
</div>
<script src="/js/jquery-3.3.1.slim.min.js"></script>
</body>
</html>

View File

@ -15,35 +15,36 @@
<div class="container"> <div class="container">
<form method="POST" action="/setup/save"> <form method="POST" action="/setup">
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label for="inputState">Database Connection</label> <label for="inputState">Database Connection</label>
<select id="inputState" name="db_connection" class="form-control"> <select id="database_type" name="db_connection" class="form-control">
<option selected value="postgres">Postgres</option> <option selected value="postgres">Postgres</option>
<option value="sqlite">Sqlite</option>
<option value="mysql">MySQL</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group" id="db_host">
<label for="formGroupExampleInput">Host</label> <label for="formGroupExampleInput">Host</label>
<input type="text" name="db_host" class="form-control" id="formGroupExampleInput" value="localhost" placeholder="localhost"> <input type="text" name="db_host" class="form-control" value="localhost" placeholder="localhost">
</div> </div>
<div class="form-group"> <div class="form-group" id="db_port">
<label for="formGroupExampleInput">Database Port</label> <label for="formGroupExampleInput">Database Port</label>
<input type="text" name="db_port" class="form-control" id="formGroupExampleInput" value="5555" placeholder="localhost"> <input type="text" name="db_port" class="form-control" value="5555" placeholder="localhost">
</div> </div>
<div class="form-group"> <div class="form-group" id="db_user">
<label for="formGroupExampleInput2">Username</label> <label for="formGroupExampleInput2">Username</label>
<input type="text" name="db_user" class="form-control" id="formGroupExampleInput2" value="root" placeholder="root"> <input type="text" name="db_user" class="form-control" value="root" placeholder="root">
</div> </div>
<div class="form-group"> <div class="form-group" id="db_password">
<label for="formGroupExampleInput2">Password</label> <label for="formGroupExampleInput2">Password</label>
<input type="password" name="db_password" class="form-control" id="formGroupExampleInput2" value="223352hunter" placeholder="password123"> <input type="password" name="db_password" class="form-control" id="formGroupExampleInput2" value="223352hunter" placeholder="password123">
</div> </div>
<div class="form-group"> <div class="form-group" id="db_database">
<label for="formGroupExampleInput2">Database</label> <label for="formGroupExampleInput2">Database</label>
<input type="text" name="db_database" class="form-control" id="formGroupExampleInput2" value="uptime" placeholder="uptimeapi"> <input type="text" name="db_database" class="form-control" id="formGroupExampleInput2" value="uptime" placeholder="uptimeapi">
</div> </div>
@ -57,6 +58,11 @@
<input type="text" name="project" class="form-control" id="formGroupExampleInput" placeholder="Great Uptime"> <input type="text" name="project" class="form-control" id="formGroupExampleInput" placeholder="Great Uptime">
</div> </div>
<div class="form-group">
<label for="formGroupExampleInput">Project Description</label>
<input type="text" name="description" class="form-control" id="formGroupExampleInput" placeholder="Great Uptime">
</div>
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Admin Username</label> <label for="formGroupExampleInput">Admin Username</label>
<input type="text" name="username" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin"> <input type="text" name="username" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin">
@ -91,5 +97,7 @@
</div> </div>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/setup.js"></script>
</body> </body>
</html> </html>

View File

@ -40,32 +40,31 @@
</table> </table>
<h3>Create User</h3> <h3>Create User</h3>
<form action="/users" method="POST">
<form action="/users" method="POST"> <div class="form-group row">
<div class="form-group row"> <label for="inputEmail3" class="col-sm-4 col-form-label">Username</label>
<label for="inputEmail3" class="col-sm-4 col-form-label">Username</label> <div class="col-sm-8">
<div class="col-sm-8"> <input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
</div>
</div> </div>
<div class="form-group row"> </div>
<label for="inputPassword3" class="col-sm-4 col-form-label">Password</label> <div class="form-group row">
<div class="col-sm-8"> <label for="inputPassword3" class="col-sm-4 col-form-label">Password</label>
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password"> <div class="col-sm-8">
</div> <input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
</div> </div>
<div class="form-group row"> </div>
<label for="inputPassword3" class="col-sm-4 col-form-label">Confirm Password</label> <div class="form-group row">
<div class="col-sm-8"> <label for="inputPassword3" class="col-sm-4 col-form-label">Confirm Password</label>
<input type="password" name="password_confirm" class="form-control" id="inputPassword3" placeholder="ConfirmPassword"> <div class="col-sm-8">
</div> <input type="password" name="password_confirm" class="form-control" id="inputPassword3" placeholder="ConfirmPassword">
</div> </div>
<div class="form-group row"> </div>
<div class="col-sm-8"> <div class="form-group row">
<button type="submit" class="btn btn-primary">Create User</button> <div class="col-sm-8">
</div> <button type="submit" class="btn btn-primary">Create User</button>
</div> </div>
</form> </div>
</form>
</div> </div>
@ -74,6 +73,6 @@
</div> </div>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

11
main.go
View File

@ -15,6 +15,7 @@ import (
"net/http" "net/http"
"os" "os"
plg "plugin" plg "plugin"
"strconv"
"strings" "strings"
) )
@ -245,6 +246,11 @@ func main() {
mainProcess() mainProcess()
} }
func StringInt(s string) int64 {
num, _ := strconv.Atoi(s)
return int64(num)
}
func mainProcess() { func mainProcess() {
var err error var err error
err = DbConnection(configs.Connection) err = DbConnection(configs.Connection)
@ -253,7 +259,8 @@ func mainProcess() {
} }
core, err = SelectCore() core, err = SelectCore()
if err != nil { if err != nil {
throw(err) fmt.Println("Core database was not found, Statup is not setup yet.")
RunHTTPServer()
} }
go CheckServices() go CheckServices()
if !setupMode { if !setupMode {
@ -263,6 +270,7 @@ func mainProcess() {
} }
func throw(err error) { func throw(err error) {
panic(err)
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
@ -334,6 +342,7 @@ func LoadConfig() (*Config, error) {
return nil, err return nil, err
} }
err = yaml.Unmarshal(file, &config) err = yaml.Unmarshal(file, &config)
configs = &config
return &config, err return &config, err
} }

View File

@ -2,8 +2,6 @@ package main
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"os" "os"
"testing" "testing"
"time" "time"
@ -12,9 +10,78 @@ import (
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
VERSION = "1.1.1" VERSION = "1.1.1"
RenderBoxes() 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{ config := &DbConfig{
"postgres", "postgres",
os.Getenv("DB_HOST"), os.Getenv("DB_HOST"),
@ -22,40 +89,139 @@ func TestMakeConfig(t *testing.T) {
os.Getenv("DB_PASS"), os.Getenv("DB_PASS"),
os.Getenv("DB_DATABASE"), os.Getenv("DB_DATABASE"),
5432, 5432,
"Testing", "Testing POSTGRES",
"This is a test of Statup.io!",
"admin", "admin",
"admin", "admin",
} }
err := config.Save() err := config.Save()
assert.Nil(t, err) 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 var err error
configs, err = LoadConfig() configs, err = LoadConfig()
assert.Nil(t, err) assert.Nil(t, err)
time.Sleep(2 * time.Second) 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")
}

View File

@ -1,10 +1,10 @@
package plugin package plugin
import ( import (
"database/sql"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"upper.io/db.v3/lib/sqlbuilder"
) )
// //
@ -16,10 +16,10 @@ import (
// //
var ( var (
DB *sql.DB DB sqlbuilder.Database
) )
func SetDatabase(database *sql.DB) { func SetDatabase(database sqlbuilder.Database) {
DB = database DB = database
} }
@ -81,7 +81,7 @@ type Service struct {
type Failure struct { type Failure struct {
Id int Id int
Issue string Issue string
Service int Service int64
CreatedAt time.Time CreatedAt time.Time
Ago string Ago string
} }

View File

@ -15,51 +15,38 @@ var (
) )
type Service struct { type Service struct {
Id int64 Id int64 `db:"id,omitempty" json:"id"`
Name string Name string `db:"name" json:"name"`
Domain string Domain string `db:"domain" json:"domain"`
Expected string Expected string `db:"expected" json:"expected"`
ExpectedStatus int ExpectedStatus int `db:"expected_status" json:"expected_status"`
Interval int Interval int `db:"check_interval" json:"check_interval"`
Method string Type string `db:"check_type" json:"type"`
Port int Method string `db:"method" json:"method"`
CreatedAt time.Time Port int `db:"port" json:"port"`
Data string CreatedAt time.Time `db:"created_at" json:"created_at"`
Online bool Online bool `json:"online"`
Latency float64 Latency float64 `json:"latency"`
Online24Hours float32 Online24Hours float32 `json:"24_hours_online"`
AvgResponse string AvgResponse string `json:"avg_response"`
TotalUptime string TotalUptime string `json:"uptime"`
Failures []*Failure Failures []*Failure `json:"failures"`
plugin.Service plugin.Service
} }
func SelectService(id string) *Service { func SelectService(id int64) (Service, error) {
for _, s := range services { var service Service
if id == strconv.Itoa(int(s.Id)) { col := dbSession.Collection("services")
return s res := col.Find("id", id)
} err := res.One(&service)
} return service, err
return nil
} }
func SelectAllServices() []*Service { func SelectAllServices() ([]*Service, error) {
var tks []*Service var services []*Service
rows, err := db.Query("SELECT * FROM services ORDER BY id ASC") col := dbSession.Collection("services").Find()
if err != nil { err := col.All(&services)
panic(err) return services, 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 (s *Service) FormatData() *Service { func (s *Service) FormatData() *Service {
@ -71,16 +58,20 @@ func (s *Service) FormatData() *Service {
} }
func (s *Service) AvgTime() float64 { func (s *Service) AvgTime() float64 {
total := s.TotalHits() total, _ := s.TotalHits()
sum := s.Sum() if total == 0 {
return float64(0)
}
sum, _ := s.Sum()
avg := sum / float64(total) * 100 avg := sum / float64(total) * 100
s.AvgResponse = fmt.Sprintf("%0.0f", avg*10) amount := fmt.Sprintf("%0.0f", avg*10)
return avg val, _ := strconv.ParseFloat(amount, 10)
return val
} }
func (s *Service) Online24() float32 { func (s *Service) Online24() float32 {
total := s.TotalHits() total, _ := s.TotalHits()
failed := s.TotalFailures24Hours() failed, _ := s.TotalFailures24Hours()
if failed == 0 { if failed == 0 {
s.Online24Hours = 100.00 s.Online24Hours = 100.00
return s.Online24Hours return s.Online24Hours
@ -105,12 +96,13 @@ type GraphJson struct {
} }
func (s *Service) GraphData() string { func (s *Service) GraphData() string {
var d []*GraphJson var d []GraphJson
for _, h := range s.Hits() { hits, _ := s.Hits()
for _, h := range hits {
val := h.CreatedAt val := h.CreatedAt
o := &GraphJson{ o := GraphJson{
X: val.String(), X: val.String(),
Y: h.Value * 1000, Y: h.Latency * 1000,
} }
d = append(d, o) d = append(d, o)
} }
@ -120,8 +112,8 @@ func (s *Service) GraphData() string {
} }
func (s *Service) AvgUptime() string { func (s *Service) AvgUptime() string {
failed := s.TotalFailures() failed, _ := s.TotalFailures()
total := s.TotalHits() total, _ := s.TotalHits()
if failed == 0 { if failed == 0 {
s.TotalUptime = "100.00" s.TotalUptime = "100.00"
return s.TotalUptime return s.TotalUptime
@ -139,27 +131,26 @@ func (s *Service) AvgUptime() string {
return s.TotalUptime return s.TotalUptime
} }
func (u *Service) Delete() { func (u *Service) Delete() error {
stmt, err := db.Prepare("DELETE FROM services WHERE id=$1") col := dbSession.Collection("services")
if err != nil { res := col.Find("id", u.Id)
panic(err) err := res.Delete()
} return err
stmt.Exec(u.Id)
} }
func (u *Service) Update() { func (u *Service) Update() {
} }
func (u *Service) Create() int { func (u *Service) Create() (int64, error) {
var lastInsertId int u.CreatedAt = time.Now()
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) col := dbSession.Collection("services")
if err != nil { uuid, err := col.Insert(u)
panic(err) services, _ = SelectAllServices()
if uuid == nil {
return 0, err
} }
services = SelectAllServices() return uuid.(int64), err
go u.CheckQueue()
return lastInsertId
} }
func CountOnline() int { func CountOnline() int {
@ -174,17 +165,13 @@ func CountOnline() int {
func NewSHA1Hash(n ...int) string { func NewSHA1Hash(n ...int) string {
noRandomCharacters := 32 noRandomCharacters := 32
if len(n) > 0 { if len(n) > 0 {
noRandomCharacters = n[0] noRandomCharacters = n[0]
} }
randString := RandomString(noRandomCharacters) randString := RandomString(noRandomCharacters)
hash := sha1.New() hash := sha1.New()
hash.Write([]byte(randString)) hash.Write([]byte(randString))
bs := hash.Sum(nil) bs := hash.Sum(nil)
return fmt.Sprintf("%x", bs) return fmt.Sprintf("%x", bs)
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"github.com/go-yaml/yaml" "github.com/go-yaml/yaml"
"github.com/hunterlong/statup/plugin"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
@ -9,15 +10,16 @@ import (
) )
type DbConfig struct { type DbConfig struct {
DbConn string `yaml:"connection"` DbConn string `yaml:"connection"`
DbHost string `yaml:"host"` DbHost string `yaml:"host"`
DbUser string `yaml:"user"` DbUser string `yaml:"user"`
DbPass string `yaml:"password"` DbPass string `yaml:"password"`
DbData string `yaml:"database"` DbData string `yaml:"database"`
DbPort int `yaml:"port"` DbPort int `yaml:"port"`
Project string `yaml:"-"` Project string `yaml:"-"`
Username string `yaml:"-"` Description string `yaml:"-"`
Password string `yaml:"-"` Username string `yaml:"-"`
Password string `yaml:"-"`
} }
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { 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") project := r.PostForm.Get("project")
username := r.PostForm.Get("username") username := r.PostForm.Get("username")
password := r.PostForm.Get("password") password := r.PostForm.Get("password")
sample := r.PostForm.Get("sample_data")
description := r.PostForm.Get("description")
config := &DbConfig{ config := &DbConfig{
dbConn, dbConn,
@ -40,20 +44,32 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
dbDatabase, dbDatabase,
dbPort, dbPort,
project, project,
description,
username, username,
password, password,
} }
err := config.Save() err := config.Save()
if err != nil { 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) http.Redirect(w, r, "/", http.StatusSeeOther)
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
mainProcess() mainProcess()
} }
func (c *DbConfig) Save() error { func (c *DbConfig) Save() error {
@ -79,6 +95,21 @@ func (c *DbConfig) Save() error {
} }
DropDatabase() DropDatabase()
CreateDatabase() 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 return err
} }

View File

@ -1,5 +1,5 @@
DROP table core; DROP table core;
DROP table hits; DROP table hits;
DROP table failures; DROP table failures;
DROP table services; DROP table users;
DROP table users; DROP table services;

47
sql/mysql_up.sql Normal file
View File

@ -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)
);

48
sql/postgres_up.sql Normal file
View File

@ -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);

48
sql/sqlite_up.sql Normal file
View File

@ -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);

View File

@ -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());

View File

@ -2,58 +2,62 @@ package main
import ( import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"time"
) )
type User struct { type User struct {
Id int64 Id int64 `db:"id,omitempty" json:"id"`
Username string Username string `db:"username" json:"username"`
Password string Password string `db:"password" json:"-"`
Email string 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 var user User
rows, err := db.Query("SELECT id, username, password FROM users WHERE username=$1", username) col := dbSession.Collection("users")
if err != nil { res := col.Find("id", id)
panic(err) err := res.One(&user)
} return &user, err
for rows.Next() {
err = rows.Scan(&user.Id, &user.Username, &user.Password)
if err != nil {
panic(err)
}
}
return user
} }
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) password := HashPassword(u.Password)
var lastInsertId int u.Password = password
db.QueryRow("INSERT INTO users(username,password,created_at) VALUES($1,$2,NOW()) returning id;", u.Username, password).Scan(&lastInsertId) u.ApiKey = NewSHA1Hash(5)
return lastInsertId 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 var users []User
rows, err := db.Query("SELECT id, username, password FROM users ORDER BY id ASC") col := dbSession.Collection("users").Find()
if err != nil { err := col.All(&users)
panic(err) return users, 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
} }
func AuthUser(username, password string) (User, bool) { func AuthUser(username, password string) (*User, bool) {
var user User
var auth bool var auth bool
user = SelectUser(username) user, err := SelectUsername(username)
if err != nil {
return nil, false
}
if CheckHash(password, user.Password) { if CheckHash(password, user.Password) {
auth = true auth = true
} }

131
web.go
View File

@ -15,28 +15,40 @@ var (
session *sessions.CookieStore session *sessions.CookieStore
) )
const (
cookieKey = "apizer_auth"
)
func Router() *mux.Router { func Router() *mux.Router {
r := mux.NewRouter() r := mux.NewRouter()
r.Handle("/", http.HandlerFunc(IndexHandler)) r.Handle("/", http.HandlerFunc(IndexHandler))
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox()))) r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox())))
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox()))) r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox())))
r.Handle("/setup", http.HandlerFunc(SetupHandler)) r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
r.Handle("/setup/save", http.HandlerFunc(ProcessSetupHandler)) r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)) r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
r.Handle("/login", http.HandlerFunc(LoginHandler)) r.Handle("/dashboard", http.HandlerFunc(LoginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(LogoutHandler)) 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("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)) r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST") r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler)) r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler)) 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("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
r.Handle("/settings", http.HandlerFunc(PluginsHandler)) r.Handle("/settings", http.HandlerFunc(PluginsHandler))
r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler)) r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler))
r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST") r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST")
r.Handle("/help", http.HandlerFunc(HelpHandler)) 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 return r
} }
@ -64,20 +76,18 @@ func RunHTTPServer() {
} }
func LogoutHandler(w http.ResponseWriter, r *http.Request) { func LogoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "apizer_auth") session, _ := store.Get(r, cookieKey)
session.Values["authenticated"] = false session.Values["authenticated"] = false
session.Save(r, w) session.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }
func LoginHandler(w http.ResponseWriter, r *http.Request) { func LoginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "apizer_auth") session, _ := store.Get(r, cookieKey)
r.ParseForm() r.ParseForm()
username := r.PostForm.Get("username") username := r.PostForm.Get("username")
password := r.PostForm.Get("password") password := r.PostForm.Get("password")
user, auth := AuthUser(username, password) _, auth := AuthUser(username, password)
fmt.Println(user)
fmt.Println(auth)
if auth { if auth {
session.Values["authenticated"] = true session.Values["authenticated"] = true
session.Save(r, w) 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) { func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("creating user")
r.ParseForm() r.ParseForm()
username := r.PostForm.Get("username") username := r.PostForm.Get("username")
password := r.PostForm.Get("password") password := r.PostForm.Get("password")
@ -114,11 +108,15 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
Username: username, Username: username,
Password: password, Password: password,
} }
user.Create() _, err := user.Create()
if err != nil {
panic(err)
}
http.Redirect(w, r, "/users", http.StatusSeeOther) http.Redirect(w, r, "/users", http.StatusSeeOther)
} }
func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("service adding")
r.ParseForm() r.ParseForm()
name := r.PostForm.Get("name") name := r.PostForm.Get("name")
domain := r.PostForm.Get("domain") 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")) status, _ := strconv.Atoi(r.PostForm.Get("expected_status"))
interval, _ := strconv.Atoi(r.PostForm.Get("interval")) interval, _ := strconv.Atoi(r.PostForm.Get("interval"))
service := &Service{ fmt.Println(r.PostForm)
service := Service{
Name: name, Name: name,
Domain: domain, Domain: domain,
Method: method, Method: method,
@ -138,7 +138,10 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(service) fmt.Println(service)
service.Create() _, err := service.Create()
if err != nil {
go service.CheckQueue()
}
http.Redirect(w, r, "/services", http.StatusSeeOther) http.Redirect(w, r, "/services", http.StatusSeeOther)
} }
@ -168,29 +171,30 @@ type dashboard struct {
Core *Core Core *Core
CountOnline int CountOnline int
CountServices int CountServices int
Count24Failures int Count24Failures uint64
} }
func DashboardHandler(w http.ResponseWriter, r *http.Request) { 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 { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
tmpl := Parse("login.html") tmpl := Parse("login.html")
tmpl.Execute(w, nil) tmpl.Execute(w, nil)
} else { } else {
tmpl := Parse("dashboard.html") 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) tmpl.Execute(w, out)
} }
} }
type serviceHandler struct { type serviceHandler struct {
Service *Service Service Service
Auth bool Auth bool
} }
func ServicesHandler(w http.ResponseWriter, r *http.Request) { 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 { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
@ -200,21 +204,21 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
} }
func ServicesDeleteHandler(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 { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
service := SelectService(vars["id"]) service, _ := SelectService(StringInt(vars["id"]))
service.Delete() service.Delete()
services = SelectAllServices() services, _ = SelectAllServices()
http.Redirect(w, r, "/services", http.StatusSeeOther) http.Redirect(w, r, "/services", http.StatusSeeOther)
} }
func IsAuthenticated(r *http.Request) bool { func IsAuthenticated(r *http.Request) bool {
session, _ := store.Get(r, "apizer_auth") session, _ := store.Get(r, cookieKey)
if session.Values["authenticated"] == nil { if session.Values["authenticated"] == nil {
return false return false
} }
@ -298,15 +302,31 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
//service := SelectService(vars["id"]) //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(`<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="104" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h50v20H54z"/><path fill="url(#b)" d="M0 0h104v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">`+service.Name+`</text><text x="28" y="14">`+service.Name+`</text><text x="78" y="15" fill="#010101" fill-opacity=".3">online</text><text x="78" y="14">online</text></g></svg>`)
} else {
badge = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="99" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#e05d44" d="M54 0h45v20H54z"/><path fill="url(#b)" d="M0 0h99v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="28" y="15" fill="#010101" fill-opacity=".3">`+service.Name+`</text><text x="28" y="14">`+service.Name+`</text><text x="75.5" y="15" fill="#010101" fill-opacity=".3">offline</text><text x="75.5" y="14">offline</text></g></svg>`)
}
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write(badge)
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) { func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r) auth := IsAuthenticated(r)
vars := mux.Vars(r) vars := mux.Vars(r)
service := SelectService(vars["id"]) service, _ := SelectService(StringInt(vars["id"]))
tmpl := Parse("service.html") tmpl := Parse("service.html")
serve := &serviceHandler{service, auth} serve := &serviceHandler{service, auth}
tmpl.Execute(w, serve) tmpl.Execute(w, serve)
} }
@ -353,28 +373,13 @@ func ParsePlugins(file string) *template.Template {
} }
func UsersHandler(w http.ResponseWriter, r *http.Request) { 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 { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
tmpl := Parse("users.html") tmpl := Parse("users.html")
tmpl.Execute(w, SelectAllUsers()) users, _ := SelectAllUsers()
} tmpl.Execute(w, users)
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())
} }

91
web_test.go Normal file
View File

@ -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")
}