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
rice-box.go
config.yml
config.yml
statup.db
plugins/*.so

View File

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

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() {
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)
}

22
core.go
View File

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

View File

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

View File

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

75
hits.go
View File

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

View File

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

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>
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>

View File

@ -12,14 +12,32 @@
</head>
<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="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">
{{ range .Services }}
<div class="col-12 mb-4">
<div class="mb-4">
<div class="card">
<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="col-4">
<span class="lg_number">{{.Online24Hours}}%</span>
<span class="lg_number">{{.Online24}}%</span>
Online last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">{{.AvgResponse}}ms</span>
<span class="lg_number">{{.AvgTime}}ms</span>
Average Response
</div>
<div class="col-4">
<span class="lg_number">{{.TotalUptime}}%</span>
<span class="lg_number">{{.AvgUptime}}%</span>
Total Uptime
</div>
</div>
@ -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 }}
</script>
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>

View File

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

View File

@ -1,6 +1,6 @@
{{define "nav"}}
<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">
<span class="navbar-toggler-icon"></span>
</button>

View File

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

View File

@ -108,6 +108,6 @@
</div>
<script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</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">
<form method="POST" action="/setup/save">
<form method="POST" action="/setup">
<div class="row">
<div class="col-6">
<div class="form-group">
<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 value="sqlite">Sqlite</option>
<option value="mysql">MySQL</option>
</select>
</div>
<div class="form-group">
<div class="form-group" id="db_host">
<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 class="form-group">
<div class="form-group" id="db_port">
<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 class="form-group">
<div class="form-group" id="db_user">
<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 class="form-group">
<div class="form-group" id="db_password">
<label for="formGroupExampleInput2">Password</label>
<input type="password" name="db_password" class="form-control" id="formGroupExampleInput2" value="223352hunter" placeholder="password123">
</div>
<div class="form-group">
<div class="form-group" id="db_database">
<label for="formGroupExampleInput2">Database</label>
<input type="text" name="db_database" class="form-control" id="formGroupExampleInput2" value="uptime" placeholder="uptimeapi">
</div>
@ -57,6 +58,11 @@
<input type="text" name="project" class="form-control" id="formGroupExampleInput" placeholder="Great Uptime">
</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">
<label for="formGroupExampleInput">Admin Username</label>
<input type="text" name="username" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin">
@ -91,5 +97,7 @@
</div>
<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>
</html>

View File

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

11
main.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
DROP table core;
DROP table hits;
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 (
"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
}

131
web.go
View File

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

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