mirror of https://github.com/statping/statping
first commit
commit
26b8ab85a5
|
@ -0,0 +1,2 @@
|
|||
.idea
|
||||
rice-box.go
|
|
@ -0,0 +1,60 @@
|
|||
os:
|
||||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.10.x"
|
||||
|
||||
install: true
|
||||
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
env:
|
||||
- VERSION=0.1
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
|
||||
before_deploy:
|
||||
- git config --local user.name "hunterlong"
|
||||
- git config --local user.email "info@socialeck.com"
|
||||
- git tag "v$VERSION" --force
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key: $GH_TOKEN
|
||||
file:
|
||||
- "build/fusioner-osx-x64"
|
||||
- "build/fusioner-osx-x32"
|
||||
- "build/fusioner-linux-x64"
|
||||
- "build/fusioner-linux-x32"
|
||||
- "build/fusioner-windows-x64.exe"
|
||||
- "build/fusioner-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
|
||||
|
||||
before_script:
|
||||
- go get github.com/stretchr/testify/assert
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/rendon/testcli
|
||||
- go get github.com/karalabe/xgo
|
||||
- go get github.com/GeertJohan/go.rice
|
||||
- go get github.com/GeertJohan/go.rice/rice
|
||||
- go get -u golang.org/x/vgo
|
||||
- vgo get -u
|
||||
|
||||
script:
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then /bin/bash -c ./build.sh; fi
|
|
@ -0,0 +1,14 @@
|
|||
FROM golang:alpine as builder
|
||||
|
||||
RUN apk update && apk add git
|
||||
|
||||
COPY . $GOPATH/src/github.com/hunterlong/fusioner/
|
||||
WORKDIR $GOPATH/src/github.com/hunterlong/fusioner/
|
||||
RUN go get github.com/GeertJohan/go.rice/rice
|
||||
RUN go get -d -v
|
||||
RUN rice embed-go
|
||||
RUN go install
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT fusioner
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
mkdir build
|
||||
|
||||
APP="fusioner"
|
||||
|
||||
rice embed-go
|
||||
|
||||
xgo --targets=darwin/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
|
||||
xgo --targets=darwin/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
|
||||
|
||||
xgo --targets=linux/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
|
||||
xgo --targets=linux/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
|
||||
|
||||
xgo --targets=windows/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
|
||||
xgo --targets=windows/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
|
||||
|
||||
mv build/$APP-darwin-10.6-amd64 build/$APP-osx-x64
|
||||
mv build/$APP-darwin-10.6-386 build/$APP-osx-x32
|
||||
mv build/$APP-linux-amd64 build/$APP-linux-x64
|
||||
mv build/$APP-linux-386 build/$APP-linux-x32
|
||||
mv build/$APP-windows-4.0-amd64.exe build/$APP-windows-x64.exe
|
||||
mv build/$APP-windows-4.0-386.exe build/$APP-windows-x32.exe
|
|
@ -0,0 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CheckServices() {
|
||||
services := SelectAllServices()
|
||||
for _, v := range services {
|
||||
obj := v
|
||||
go obj.CheckQueue()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CheckQueue() {
|
||||
time.Sleep(time.Duration(s.Interval) * time.Second)
|
||||
s.Check()
|
||||
fmt.Printf("Service: %v | Online: %v | Latency: %v\n", s.Name, s.Online, s.Latency)
|
||||
s.CheckQueue()
|
||||
}
|
||||
|
||||
func (s *Service) Check() {
|
||||
t1 := time.Now()
|
||||
response, err := http.Get(s.Domain)
|
||||
t2 := time.Now()
|
||||
s.Latency = t2.Sub(t1).Seconds()
|
||||
if err != nil {
|
||||
s.Failure(response, fmt.Sprintf("HTTP Error %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if s.Expected != "" {
|
||||
contents, _ := ioutil.ReadAll(response.Body)
|
||||
match, _ := regexp.MatchString(s.Expected, string(contents))
|
||||
if !match {
|
||||
s.Failure(response, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
|
||||
return
|
||||
}
|
||||
}
|
||||
if s.ExpectedStatus != response.StatusCode {
|
||||
s.Failure(response, fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus))
|
||||
return
|
||||
}
|
||||
s.Record(response)
|
||||
}
|
||||
|
||||
func (s *Service) Record(response *http.Response) {
|
||||
defer response.Body.Close()
|
||||
s.Online = true
|
||||
db.QueryRow("INSERT INTO hits(service,latency,created_at) VALUES($1,$2,NOW()) returning id;", s.Id, s.Latency).Scan()
|
||||
}
|
||||
|
||||
func (s *Service) Failure(response *http.Response, issue string) {
|
||||
db.QueryRow("INSERT INTO failures(issue,service,created_at) VALUES($1,$2,NOW()) returning id;", issue, s.Id).Scan()
|
||||
s.Record(response)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
|
||||
type Core struct {
|
||||
Name string
|
||||
Config string
|
||||
Key string
|
||||
Secret string
|
||||
Version string
|
||||
}
|
||||
|
||||
|
||||
func SelectCore() (*Core, error) {
|
||||
var core Core
|
||||
rows, err := db.Query("SELECT * FROM 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
|
||||
}
|
||||
}
|
||||
return &core, err
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/lib/pq"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DbConnection() {
|
||||
var err error
|
||||
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)
|
||||
db, err = sql.Open("postgres", dbinfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabase() {
|
||||
fmt.Println("New Version: ", core.Version)
|
||||
fmt.Println("Current Version: ", VERSION)
|
||||
if VERSION == core.Version {
|
||||
fmt.Println("Database already up to date")
|
||||
return
|
||||
}
|
||||
fmt.Println("Upgrading Database...")
|
||||
upgrade, _ := sqlBox.String("upgrade.sql")
|
||||
db.QueryRow(upgrade).Scan()
|
||||
}
|
||||
|
||||
func DropDatabase() {
|
||||
fmt.Println("Dropping Tables...")
|
||||
down, _ := sqlBox.String("down.sql")
|
||||
db.QueryRow(down).Scan()
|
||||
}
|
||||
|
||||
func CreateDatabase() {
|
||||
fmt.Println("Creating Tables...")
|
||||
VERSION = "1.1.1"
|
||||
up, _ := sqlBox.String("up.sql")
|
||||
db.QueryRow(up).Scan()
|
||||
//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++
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
type Failure struct {
|
||||
Id int
|
||||
Issue string
|
||||
Service int
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func SelectAllFailures(id int64) []float64 {
|
||||
var tks []float64
|
||||
rows, err := db.Query("SELECT * FROM failures WHERE service=$1 ORDER BY id ASC", 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.Value)
|
||||
}
|
||||
return tks
|
||||
}
|
||||
|
||||
func (s *Service) TotalFailures() int {
|
||||
var amount int
|
||||
db.QueryRow("SELECT COUNT(id) FROM failures WHERE service=$1;", s.Id).Scan(&amount)
|
||||
return amount
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
module github.com/hunterlong/apizer
|
||||
|
||||
require (
|
||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da
|
||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb
|
||||
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
|
||||
github.com/gorilla/sessions v1.1.1
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||
github.com/lib/pq v0.0.0-20180523175426-90697d60dd84
|
||||
golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4
|
||||
)
|
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
type Hit struct {
|
||||
Id int
|
||||
Metric int
|
||||
Value float64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func SelectAllHits(id int64) []Hit {
|
||||
var tks []Hit
|
||||
rows, err := db.Query("SELECT * FROM hits WHERE service=$1 ORDER BY id ASC", 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) TotalHits() int {
|
||||
var amount int
|
||||
db.QueryRow("SELECT COUNT(id) FROM hits WHERE service=$1;", s.Id).Scan(&amount)
|
||||
return amount
|
||||
}
|
||||
|
||||
func (s *Service) Sum() float64 {
|
||||
var amount float64
|
||||
db.QueryRow("SELECT SUM(latency) FROM hits WHERE service=$1;", s.Id).Scan(&amount)
|
||||
return amount
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
HTML,BODY {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 790px;
|
||||
margin-top: 40px;
|
||||
background-color: white;
|
||||
padding: 50px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
margin-left: -50px;
|
||||
width: 720px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.lg_number {
|
||||
font-size: 26pt;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stats_area {
|
||||
text-align: center;
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,67 @@
|
|||
<!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>Hello, world!</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<form action="/login" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-2"></div>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="gridCheck1">
|
||||
<label class="form-check-label" for="gridCheck1">
|
||||
Remember this computer
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,67 @@
|
|||
<!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>Fusioner | Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="#">Fusioner</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>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/tokens">Tokens</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users">Users</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/permissions">Permissions</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="row stats_area">
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">69</span>
|
||||
24 Hour Hits
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">3921</span>
|
||||
24 Hour Hits
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">453</span>
|
||||
Total Tokens
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,109 @@
|
|||
<!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">
|
||||
<script src="/js/Chart.bundle.min.js"></script>
|
||||
|
||||
<title>Fusioner | Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
{{ range .Services }}
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3>{{ .Name }} <span class="badge badge-secondary float-right">{{if .Online}} ONLINE {{ else }} OFFLINE {{end}}</span></h3>
|
||||
|
||||
<div class="row stats_area mt-3 mb-3">
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{.Online24Hours}}%</span>
|
||||
Online last 24 Hours
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{.AvgResponse}}ms</span>
|
||||
Average Response
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{.TotalUptime}}%</span>
|
||||
Total Uptime
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<canvas id="service_{{ .Id }}" width="400" height="120"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
{{ range .Services }}
|
||||
var ctx = document.getElementById("service_{{.Id}}").getContext('2d');
|
||||
|
||||
var chartdata = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
label: 'Response Time (Milliseconds)',
|
||||
data: {{js .Data}},
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
'rgba(54, 162, 235, 0.2)',
|
||||
'rgba(255, 206, 86, 0.2)',
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(153, 102, 255, 0.2)',
|
||||
'rgba(255, 159, 64, 0.2)'
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255,99,132,1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)',
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(153, 102, 255, 1)',
|
||||
'rgba(255, 159, 64, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
distribution: 'series'
|
||||
}]
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
{{ end }}
|
||||
</script>
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,95 @@
|
|||
<!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>Fusioner | Permissions</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="#">Fusioner</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>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/services">Services</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/users">Users</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/permissions">Permissions</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<h3>Users</h3>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Username</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<th scope="row">{{.Id}}</th>
|
||||
<td>{{.Username}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form action="/user/create" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Create User</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,83 @@
|
|||
<!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>Fusioner | Tokens</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="#">Fusioner</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>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/tokens">Tokens</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users">Users</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/permissions">Permissions</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<h3>Tokens</h3>
|
||||
|
||||
<a href="/token/create" class="btn btn-primary">Create New Token</a>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Key</th>
|
||||
<th scope="col">Secret</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<th scope="row">{{.Id}}</th>
|
||||
<td>{{.Key}}</td>
|
||||
<td>{{.Secret}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,96 @@
|
|||
<!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">
|
||||
<script src="/js/Chart.bundle.min.js"></script>
|
||||
|
||||
<title>Fusioner | Setup</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
<form method="POST" action="/setup/save">
|
||||
|
||||
<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">
|
||||
<option selected value="postgres">Postgres</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput">Host</label>
|
||||
<input type="text" name="db_host" class="form-control" id="formGroupExampleInput" value="localhost" placeholder="localhost">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput">Database Port</label>
|
||||
<input type="text" name="db_port" class="form-control" id="formGroupExampleInput" value="5555" placeholder="localhost">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput2">Username</label>
|
||||
<input type="text" name="db_user" class="form-control" id="formGroupExampleInput2" value="root" placeholder="root">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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">
|
||||
<label for="formGroupExampleInput2">Database</label>
|
||||
<input type="text" name="db_database" class="form-control" id="formGroupExampleInput2" value="uptime" placeholder="uptimeapi">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput">Project Name</label>
|
||||
<input type="text" name="project" 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">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput">Admin Password</label>
|
||||
<input type="password" name="password" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput">Confirm Password</label>
|
||||
<input type="password" name="confirm_password" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="gridCheck1">
|
||||
Load Sample Data
|
||||
</label>
|
||||
<input name="sample_data" class="form-control" type="checkbox" id="gridCheck1" checked>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,96 @@
|
|||
<!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>Fusioner | Users</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="#">Fusioner</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>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/tokens">Tokens</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/users">Users</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/permissions">Permissions</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<h3>Users</h3>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Username</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<th scope="row">{{.Id}}</th>
|
||||
<td>{{.Username}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form action="/user/create" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Create User</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/GeertJohan/go.rice"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/gorilla/sessions"
|
||||
_ "github.com/lib/pq"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
db *sql.DB
|
||||
configs *Config
|
||||
core *Core
|
||||
store *sessions.CookieStore
|
||||
VERSION string
|
||||
sqlBox *rice.Box
|
||||
cssBox *rice.Box
|
||||
jsBox *rice.Box
|
||||
tmplBox *rice.Box
|
||||
setupMode bool
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Connection string `yaml:"connection"`
|
||||
Host string `yaml:"host"`
|
||||
Database string `yaml:"database"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
Port string `yaml:"port"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
VERSION = "1.1.1"
|
||||
RenderBoxes()
|
||||
configs = LoadConfig()
|
||||
if configs == nil {
|
||||
fmt.Println("config.yml file not found - starting in setup mode")
|
||||
setupMode = true
|
||||
RunHTTPServer()
|
||||
}
|
||||
mainProcess()
|
||||
}
|
||||
|
||||
|
||||
func mainProcess() {
|
||||
var err error
|
||||
DbConnection()
|
||||
core, err = SelectCore()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go CheckServices()
|
||||
if !setupMode {
|
||||
RunHTTPServer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func RenderBoxes() {
|
||||
sqlBox = rice.MustFindBox("sql")
|
||||
cssBox = rice.MustFindBox("html/css")
|
||||
jsBox = rice.MustFindBox("html/js")
|
||||
tmplBox = rice.MustFindBox("html/tmpl")
|
||||
}
|
||||
|
||||
func LoadConfig() *Config {
|
||||
var config Config
|
||||
file, err := ioutil.ReadFile("config.yml")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
yaml.Unmarshal(file, &config)
|
||||
store = sessions.NewCookieStore([]byte(config.Secret))
|
||||
return &config
|
||||
}
|
||||
|
||||
func HashPassword(password string) string {
|
||||
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return string(bytes)
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 float64
|
||||
AvgResponse string
|
||||
TotalUptime float64
|
||||
}
|
||||
|
||||
func SelectService(id string) Service {
|
||||
var tk Service
|
||||
rows, err := db.Query("SELECT * FROM services WHERE id=$1", id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for rows.Next() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
return tk
|
||||
}
|
||||
|
||||
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.FormatData()
|
||||
tks = append(tks, tk)
|
||||
}
|
||||
return tks
|
||||
}
|
||||
|
||||
func (s *Service) FormatData() *Service {
|
||||
s.GraphData()
|
||||
s.AvgUptime()
|
||||
s.AvgTime()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Service) AvgTime() float64 {
|
||||
total := s.TotalHits()
|
||||
sum := s.Sum()
|
||||
avg := sum / float64(total) * 100
|
||||
s.AvgResponse = fmt.Sprintf("%0.0f", avg*10)
|
||||
return avg
|
||||
}
|
||||
|
||||
type GraphJson struct {
|
||||
X string `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
|
||||
func (s *Service) GraphData() string {
|
||||
hits := SelectAllHits(s.Id)
|
||||
var d []*GraphJson
|
||||
for _, h := range hits {
|
||||
val := h.CreatedAt
|
||||
o := &GraphJson{
|
||||
X: val.String(),
|
||||
Y: h.Value * 1000,
|
||||
}
|
||||
d = append(d, o)
|
||||
}
|
||||
data, _ := json.Marshal(d)
|
||||
s.Data = string(data)
|
||||
return s.Data
|
||||
}
|
||||
|
||||
func (s *Service) AvgUptime() float64 {
|
||||
failed := s.TotalFailures()
|
||||
total := s.TotalHits()
|
||||
if failed == 0 {
|
||||
s.TotalUptime = 100.00
|
||||
return s.TotalUptime
|
||||
}
|
||||
percent := float64(failed) / float64(total) * 100
|
||||
fmt.Println(failed, total, percent)
|
||||
s.TotalUptime = percent
|
||||
return percent
|
||||
}
|
||||
|
||||
func (u *Service) Create() int {
|
||||
var lastInsertId int
|
||||
db.QueryRow("INSERT INTO services(name, domain, expected, expected_status, created_at) VALUES($1,$2,$3,$4,NOW()) returning id;", u.Name, u.Domain, u.Expected, u.ExpectedStatus).Scan(&lastInsertId)
|
||||
return lastInsertId
|
||||
}
|
||||
|
||||
// NewSHA1Hash generates a new SHA1 hash based on
|
||||
// a random number of characters.
|
||||
func NewSHA1Hash(n ...int) string {
|
||||
noRandomCharacters := 32
|
||||
|
||||
if len(n) > 0 {
|
||||
noRandomCharacters = n[0]
|
||||
}
|
||||
|
||||
randString := RandomString(noRandomCharacters)
|
||||
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(randString))
|
||||
bs := hash.Sum(nil)
|
||||
|
||||
return fmt.Sprintf("%x", bs)
|
||||
}
|
||||
|
||||
var characterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
||||
// RandomString generates a random string of n length
|
||||
func RandomString(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = characterRunes[rand.Intn(len(characterRunes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"os"
|
||||
"github.com/go-yaml/yaml"
|
||||
"time"
|
||||
)
|
||||
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
dbHost := r.PostForm.Get("db_host")
|
||||
dbUser := r.PostForm.Get("db_user")
|
||||
dbPass := r.PostForm.Get("db_password")
|
||||
dbDatabase := r.PostForm.Get("db_database")
|
||||
dbConn := r.PostForm.Get("db_connection")
|
||||
dbPort, _ := strconv.Atoi(r.PostForm.Get("db_port"))
|
||||
project := r.PostForm.Get("project")
|
||||
username := r.PostForm.Get("username")
|
||||
password := r.PostForm.Get("password")
|
||||
|
||||
config := &DbConfig{
|
||||
dbConn,
|
||||
dbHost,
|
||||
dbUser,
|
||||
dbPass,
|
||||
dbDatabase,
|
||||
dbPort,
|
||||
project,
|
||||
username,
|
||||
password,
|
||||
}
|
||||
|
||||
err := config.Save()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
mainProcess()
|
||||
|
||||
}
|
||||
|
||||
|
||||
func (c *DbConfig) Save() error {
|
||||
var err error
|
||||
config, err := os.Create("config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.WriteString(string(data))
|
||||
config.Close()
|
||||
|
||||
configs = LoadConfig()
|
||||
|
||||
DbConnection()
|
||||
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()
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
DROP table core;
|
||||
DROP table hits;
|
||||
DROP table failures;
|
||||
DROP table services;
|
||||
DROP table users;
|
|
@ -0,0 +1,60 @@
|
|||
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, 'Google', 'https://www.google.com', '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());
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
|
||||
func SelectUser(username string) User {
|
||||
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
|
||||
}
|
||||
|
||||
func (u *User) Create() int {
|
||||
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
|
||||
}
|
||||
|
||||
func SelectAllUsers() []User {
|
||||
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
|
||||
}
|
||||
|
||||
func AuthUser(username, password string) (User, bool) {
|
||||
var user User
|
||||
var auth bool
|
||||
user = SelectUser(username)
|
||||
if CheckHash(password, user.Password) {
|
||||
auth = true
|
||||
}
|
||||
return user, auth
|
||||
}
|
||||
|
||||
func CheckHash(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type dashboard struct {
|
||||
Services []Service
|
||||
Users []User
|
||||
Core *Core
|
||||
}
|
||||
|
||||
func RunHTTPServer() {
|
||||
fmt.Println("Fusioner HTTP Server running on http://localhost:9595")
|
||||
css := http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox()))
|
||||
js := http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox()))
|
||||
http.Handle("/", http.HandlerFunc(IndexHandler))
|
||||
http.Handle("/css/", css)
|
||||
http.Handle("/js/", js)
|
||||
http.Handle("/setup", http.HandlerFunc(SetupHandler))
|
||||
http.Handle("/setup/save", http.HandlerFunc(ProcessSetupHandler))
|
||||
http.Handle("/dashboard", http.HandlerFunc(DashboardHandler))
|
||||
http.Handle("/login", http.HandlerFunc(LoginHandler))
|
||||
http.Handle("/logout", http.HandlerFunc(LogoutHandler))
|
||||
//http.Handle("/auth", http.HandlerFunc(AuthenticateHandler))
|
||||
http.Handle("/user/create", http.HandlerFunc(CreateUserHandler))
|
||||
http.Handle("/token/create", http.HandlerFunc(CreateServiceHandler))
|
||||
http.Handle("/tokens", http.HandlerFunc(ServicesHandler))
|
||||
http.Handle("/users", http.HandlerFunc(UsersHandler))
|
||||
http.ListenAndServe(":9595", nil)
|
||||
}
|
||||
|
||||
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := store.Get(r, "apizer_auth")
|
||||
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")
|
||||
r.ParseForm()
|
||||
username := r.PostForm.Get("username")
|
||||
password := r.PostForm.Get("password")
|
||||
_, auth := AuthUser(username, password)
|
||||
if auth {
|
||||
session.Values["authenticated"] = true
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||
} else {
|
||||
w.WriteHeader(502)
|
||||
w.Header().Set("Content-Type", "plain/text")
|
||||
fmt.Fprintln(w, "bad")
|
||||
}
|
||||
}
|
||||
|
||||
//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) {
|
||||
r.ParseForm()
|
||||
username := r.PostForm.Get("username")
|
||||
password := r.PostForm.Get("password")
|
||||
user := &User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
user.Create()
|
||||
http.Redirect(w, r, "/users", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := &Service{}
|
||||
token.Create()
|
||||
http.Redirect(w, r, "/services", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
|
||||
func SetupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
setupFile, err := tmplBox.String("setup.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
setupTmpl, err := template.New("message").Parse(setupFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
setupTmpl.Execute(w, nil)
|
||||
}
|
||||
|
||||
type index struct {
|
||||
Services []Service
|
||||
}
|
||||
|
||||
func IndexHandler(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
|
||||
//}
|
||||
|
||||
if setupMode {
|
||||
http.Redirect(w, r, "/setup", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
dashboardFile, err := tmplBox.String("index.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dashboardTmpl, err := template.New("message").Funcs(template.FuncMap{
|
||||
"js": func(html string) template.JS {
|
||||
return template.JS(html)
|
||||
},
|
||||
}).Parse(dashboardFile)
|
||||
|
||||
out := index{SelectAllServices()}
|
||||
|
||||
dashboardTmpl.Execute(w, out)
|
||||
}
|
||||
|
||||
func DashboardHandler(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
|
||||
//}
|
||||
|
||||
dashboardFile, err := tmplBox.String("dashboard.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dashboardTmpl, err := template.New("message").Parse(dashboardFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
out := dashboard{SelectAllServices(), SelectAllUsers(), core}
|
||||
|
||||
dashboardTmpl.Execute(w, out)
|
||||
}
|
||||
|
||||
func ServicesHandler(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
|
||||
}
|
||||
tokensFile, err := tmplBox.String("services.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tokensTmpl, err := template.New("message").Parse(tokensFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tokensTmpl.Execute(w, SelectAllServices())
|
||||
}
|
||||
|
||||
func UsersHandler(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
|
||||
}
|
||||
usersFile, err := tmplBox.String("users.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
usersTmpl, err := template.New("message").Parse(usersFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
usersTmpl.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())
|
||||
}
|
Loading…
Reference in New Issue