upgrades - plugins

pull/10/head
Hunter Long 2018-06-18 21:48:25 -07:00
parent 6bbf636bed
commit 8a6f6a2cf8
25 changed files with 400 additions and 400 deletions

View File

@ -17,7 +17,7 @@ services:
- mongodb - mongodb
env: env:
- VERSION=0.151 - VERSION=0.16
matrix: matrix:
allow_failures: allow_failures:
@ -37,7 +37,6 @@ deploy:
- "build/statup-linux-x64" - "build/statup-linux-x64"
- "build/statup-linux-x32" - "build/statup-linux-x32"
- "build/statup-windows-x64.exe" - "build/statup-windows-x64.exe"
- "build/statup-linux-static"
skip_cleanup: true skip_cleanup: true
notifications: notifications:

View File

@ -6,13 +6,13 @@ APP="statup"
rice embed-go rice embed-go
xgo --targets=darwin/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./ xgo -go 1.10.x --targets=darwin/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
xgo --targets=darwin/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./ xgo -go 1.10.x --targets=darwin/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
xgo --targets=linux/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./ xgo -go 1.10.x --targets=linux/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
xgo --targets=linux/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./ xgo -go 1.10.x --targets=linux/386 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
xgo --targets=windows/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./ xgo -go 1.10.x --targets=windows/amd64 --dest=build -ldflags="-X main.VERSION=$VERSION" ./
cd build cd build
ls ls

View File

@ -11,6 +11,8 @@ type Core struct {
Config string `db:"config"` Config string `db:"config"`
ApiKey string `db:"api_key"` ApiKey string `db:"api_key"`
ApiSecret string `db:"api_secret"` ApiSecret string `db:"api_secret"`
Style string `db:"style"`
Footer string `db:"footer"`
Version string `db:"version"` Version string `db:"version"`
Plugins []plugin.Info Plugins []plugin.Info
Repos []PluginJSON Repos []PluginJSON

View File

@ -2,8 +2,8 @@ package main
import ( import (
"fmt" "fmt"
"github.com/hunterlong/statup/plugin"
"strings" "strings"
"time"
"upper.io/db.v3/lib/sqlbuilder" "upper.io/db.v3/lib/sqlbuilder"
"upper.io/db.v3/mysql" "upper.io/db.v3/mysql"
"upper.io/db.v3/postgresql" "upper.io/db.v3/postgresql"
@ -29,6 +29,9 @@ func DbConnection(dbType string) error {
return err return err
} }
} else if dbType == "mysql" { } else if dbType == "mysql" {
if configs.Port == "" {
configs.Port = "3306"
}
mysqlSettings = mysql.ConnectionURL{ mysqlSettings = mysql.ConnectionURL{
Database: configs.Database, Database: configs.Database,
Host: configs.Host, Host: configs.Host,
@ -40,9 +43,13 @@ func DbConnection(dbType string) error {
return err return err
} }
} else { } else {
if configs.Port == "" {
configs.Port = "5432"
}
host := fmt.Sprintf("%v:%v", configs.Host, configs.Port)
postgresSettings = postgresql.ConnectionURL{ postgresSettings = postgresql.ConnectionURL{
Database: configs.Database, Database: configs.Database,
Host: configs.Host, Host: host,
User: configs.User, User: configs.User,
Password: configs.Password, Password: configs.Password,
} }
@ -53,28 +60,10 @@ func DbConnection(dbType string) error {
} }
//dbSession.SetLogging(true) //dbSession.SetLogging(true)
dbServer = dbType dbServer = dbType
plugin.SetDatabase(dbSession) OnLoad(dbSession)
return err return 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")
requests := strings.Split(upgrade, ";")
for _, request := range requests {
_, err := db.Exec(request)
if err != nil {
fmt.Println(err)
}
}
}
func DropDatabase() { func DropDatabase() {
fmt.Println("Dropping Tables...") fmt.Println("Dropping Tables...")
down, _ := sqlBox.String("down.sql") down, _ := sqlBox.String("down.sql")
@ -124,22 +113,17 @@ func LoadSampleData() error {
Type: "https", Type: "https",
Method: "GET", Method: "GET",
} }
admin := &User{
Username: "admin",
Password: "admin",
Email: "admin@admin.com",
}
s1.Create() s1.Create()
s2.Create() s2.Create()
s3.Create() s3.Create()
s4.Create() s4.Create()
admin.Create()
for i := 0; i < 20; i++ { for i := 0; i < 100; i++ {
s1.Check() s1.Check()
s2.Check() s2.Check()
s3.Check() s3.Check()
s4.Check() s4.Check()
time.Sleep(250 * time.Millisecond)
} }
return nil return nil
@ -147,7 +131,6 @@ func LoadSampleData() error {
func CreateDatabase() { func CreateDatabase() {
fmt.Println("Creating Tables...") fmt.Println("Creating Tables...")
VERSION = "1.1.1"
sql := "postgres_up.sql" sql := "postgres_up.sql"
if dbServer == "mysql" { if dbServer == "mysql" {
sql = "mysql_up.sql" sql = "mysql_up.sql"

View File

@ -1,16 +1,56 @@
package main package main
import "github.com/hunterlong/statup/plugin" import (
"github.com/fatih/structs"
"github.com/hunterlong/statup/plugin"
"upper.io/db.v3/lib/sqlbuilder"
)
func OnLoad(db sqlbuilder.Database) {
for _, p := range allPlugins {
p.OnLoad(db)
}
}
func OnSuccess(s *Service) { func OnSuccess(s *Service) {
for _, p := range allPlugins { for _, p := range allPlugins {
p.OnSuccess(s.ToP()) p.OnSuccess(structs.Map(s))
} }
} }
func OnFailure(s *Service) { func OnFailure(s *Service) {
for _, p := range allPlugins { for _, p := range allPlugins {
p.OnFailure(s.ToP()) p.OnFailure(structs.Map(s))
}
}
func OnSettingsSaved(c *Core) {
for _, p := range allPlugins {
p.OnSettingsSaved(structs.Map(c))
}
}
func OnNewUser(u *User) {
for _, p := range allPlugins {
p.OnNewUser(structs.Map(u))
}
}
func OnNewService(s *Service) {
for _, p := range allPlugins {
p.OnNewService(structs.Map(s))
}
}
func OnDeletedService(s *Service) {
for _, p := range allPlugins {
p.OnDeletedService(structs.Map(s))
}
}
func OnUpdateService(s *Service) {
for _, p := range allPlugins {
p.OnUpdatedService(structs.Map(s))
} }
} }
@ -22,40 +62,3 @@ func SelectPlugin(name string) plugin.PluginActions {
} }
return plugin.PluginInfo{} return plugin.PluginInfo{}
} }
func (s *Service) PluginFailures() []*plugin.Failure {
var failed []*plugin.Failure
for _, f := range s.Failures {
fail := &plugin.Failure{
f.Id,
f.Issue,
f.Service,
f.CreatedAt,
f.Ago,
}
failed = append(failed, fail)
}
return failed
}
func (s *Service) ToP() *plugin.Service {
out := &plugin.Service{
s.Id,
s.Name,
s.Domain,
s.Expected,
s.ExpectedStatus,
s.Interval,
s.Method,
s.Port,
s.CreatedAt,
s.Data,
s.Online,
s.Latency,
s.Online24Hours,
s.AvgResponse,
s.TotalUptime,
s.PluginFailures(),
}
return out
}

View File

@ -29,16 +29,21 @@ func (s *Service) CreateFailure(data FailureData) (int64, error) {
func (s *Service) SelectAllFailures() ([]*Failure, error) { func (s *Service) SelectAllFailures() ([]*Failure, error) {
var fails []*Failure var fails []*Failure
col := dbSession.Collection("failures").Find("session", s.Id) col := dbSession.Collection("failures").Find("service", s.Id)
err := col.All(&fails) err := col.All(&fails)
return fails, err return fails, err
} }
func (s *Service) LimitedFailures() ([]*Failure, error) { func (s *Service) LimitedFailures() []*Failure {
var fails []*Failure var fails []*Failure
col := dbSession.Collection("failures").Find("session", s.Id).Limit(10) col := dbSession.Collection("failures").Find("service", s.Id).Limit(10)
err := col.All(&fails) col.All(&fails)
return fails, err return fails
}
func (f *Failure) Delete() error {
col := dbSession.Collection("failures").Find("id", f.Id)
return col.Delete()
} }
func CountFailures() (uint64, error) { func CountFailures() (uint64, error) {

View File

@ -7,20 +7,49 @@
<link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
<title>Statup | Dashboard</title> <title>Statup | Help</title>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
{{if Auth}}
{{template "nav"}} {{template "nav"}}
{{end}}
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h3>Help</h3> <h2>Statup v{{ VERSION }} Help</h2>
Statup is an easy to use Status Page monitor for your websites and applications. Statup is developed in Go Language and you are able to create custom plugins with it!
<p>
<a href="https://github.com/hunterlong/statup"><img src="https://img.shields.io/github/stars/hunterlong/statup.svg?style=social&label=Stars"></a>
<a href="https://github.com/hunterlong/statup"><img src="https://img.shields.io/docker/build/hunterlong/statup.svg"></a>
<a href="https://github.com/hunterlong/statup"><img src="https://img.shields.io/github/release/hunterlong/statup.svg"></a>
</p>
<h2 class="mt-3">Services</h2>
For each website and application you want to add a new Service. Each Service will require a URL endpoint to test your applications status.
You can also add expected HTTP responses (regex allow), expected HTTP response codes, and other fields to make sure your service is online or offline.
<h2 class="mt-3">Users</h2>
Users can access the Statup Dashboard to add, remove, and view services.
<h2 class="mt-3">Plugins</h2>
Creating a plugin for Statup is not that difficult, if you know a little bit of Go Language you can create any type of application to be embedded into the Status framework.
Checkout the example plugin that includes all the interfaces, information, and custom HTTP routing at <a href="https://github.com/hunterlong/statup_plugin">https://github.com/hunterlong/statup_plugin</a>.
Anytime there is an action on your status page, all of your plugins will be notified of the change with the values that were changed or created.
<p></p>
Using the statup/plugin Golang package you can quickly implement the event listeners. Statup uses <a href="https://github.com/upper/db">upper.io/db.v3</a> for the database connection.
You can use the database inside of your plugin to create, update, and destroy tables/data. <b>Please only use respectable plugins!</b>
<h2 class="mt-3">Custom Stlying</h2>
On Statup Status Page server can you create your own custom stylesheet to be rendered on the index view of your status page. Go to <a href="/settings">Settings</a> and click on Custom Styling.
</div>
</div> </div>

View File

@ -8,15 +8,20 @@
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
<script src="/js/Chart.bundle.min.js"></script> <script src="/js/Chart.bundle.min.js"></script>
<title>{{.Project}} Status</title> <title>{{.Core.Name}} Status</title>
</head> </head>
<body> <body>
<h1 class="text-center mb-4 mt-sm-3">{{.Project}}</h1>
<h1 class="text-center mb-4 mt-sm-3">{{.Core.Name}}</h1>
{{ if .Core.Description }}
<h5 class="text-center mb-1 mt-sm-2">{{ .Core.Description }}</h5>
{{ end }}
<div class="container"> <div class="container">
<div class="col-12 mb-5"> <div class="col-12 mb-5">
<div class="list-group online_list"> <div class="list-group online_list">
@ -72,7 +77,7 @@
<canvas id="service_{{ .Id }}" width="400" height="120"></canvas> <canvas id="service_{{ .Id }}" width="400" height="120"></canvas>
{{ range .Failures }} {{ range .LimitedFailures }}
<blockquote class="blockquote text-right mt-3"> <blockquote class="blockquote text-right mt-3">
<p class="mb-0">{{.ParseError}}</p> <p class="mb-0">{{.ParseError}}</p>
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer> <footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
@ -89,7 +94,10 @@
</div> </div>
<div class="footer text-center"> <div class="footer text-center">
<a href="https://statup.io" target="_blank">Created with Statup.io</a> | <a href="/dashboard">Dashboard</a> {{ if .Core.Footer }}
{{ safe .Core.Footer }}
{{ end }}
<a href="https://statup.io" target="_blank">Statup made with ❤️</a> | <a href="/dashboard">Dashboard</a>
</div> </div>
<script> <script>
@ -151,6 +159,14 @@ var chartdata = new Chart(ctx, {
}); });
{{ end }} {{ end }}
</script> </script>
{{ if .Core.Style }}
<style>
{{ safe .Core.Style }}
</style>
{{ end }}
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script> <script src="/js/bootstrap.min.js"></script>
</body> </body>

View File

@ -23,7 +23,8 @@
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical"> <div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a> <a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
<a class="nav-link" id="v-pills-browse-tab" data-toggle="pill" href="#v-pills-browse" role="tab" aria-controls="v-pills-home" aria-selected="true">Browse Plugins</a> <a class="nav-link" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-style" role="tab" aria-controls="v-pills-style" aria-selected="false">Styling</a>
<a class="nav-link" id="v-pills-browse-tab" data-toggle="pill" href="#v-pills-browse" role="tab" aria-controls="v-pills-home" aria-selected="false">Browse Plugins</a>
{{ range .Plugins }} {{ range .Plugins }}
<a class="nav-link text-capitalize" id="v-pills-{{.Name}}-tab" data-toggle="pill" href="#v-pills-{{.Name}}" role="tab" aria-controls="v-pills-profile" aria-selected="false">{{.Name}}</a> <a class="nav-link text-capitalize" id="v-pills-{{.Name}}-tab" data-toggle="pill" href="#v-pills-{{.Name}}" role="tab" aria-controls="v-pills-profile" aria-selected="false">{{.Name}}</a>
{{end}} {{end}}
@ -46,12 +47,31 @@
<input type="text" name="description" class="form-control" value="{{ .Description }}" id="formGroupExampleInput" placeholder="Great Uptime"> <input type="text" name="description" class="form-control" value="{{ .Description }}" id="formGroupExampleInput" placeholder="Great Uptime">
</div> </div>
<div class="form-group">
<label for="formGroupExampleInput">Custom Footer</label>
<textarea rows="4" name="footer" class="form-control" id="formGroupExampleInput">{{ .Footer }}</textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Save Settings</button> <button type="submit" class="btn btn-primary btn-block">Save Settings</button>
</form> </form>
</div> </div>
<div class="tab-pane fade" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
<h3>Custom Style</h3>
<form method="POST" action="/settings">
<div class="form-group">
<textarea rows="15" name="style" class="form-control" id="formGroupExampleInput">{{ .Style }}</textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Save Settings</button>
</form>
</div>
<div class="tab-pane fade" id="v-pills-browse" role="tabpanel" aria-labelledby="v-pills-browse-tab"> <div class="tab-pane fade" id="v-pills-browse" role="tabpanel" aria-labelledby="v-pills-browse-tab">
{{ range .Repos }} {{ range .Repos }}
<div class="card col-6" style="width: 18rem;"> <div class="card col-6" style="width: 18rem;">
@ -98,5 +118,6 @@
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script> <script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -12,10 +12,9 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
{{if .Auth}} {{if Auth}}
{{template "nav"}} {{template "nav"}}
{{end}} {{end}}
<div class="row"> <div class="row">
@ -24,8 +23,8 @@
<div class="col-12 mb-4"> <div class="col-12 mb-4">
<h3 class="mt-2">{{ .Service.Name }} <h3 class="mt-2">{{ .Name }}
{{if .Service.Online}} {{if .Online }}
<span class="badge online_badge float-right">ONLINE</span> <span class="badge online_badge float-right">ONLINE</span>
{{ else }} {{ else }}
<span class="badge offline_badge float-right">OFFLINE</span> <span class="badge offline_badge float-right">OFFLINE</span>
@ -34,24 +33,24 @@
<div class="row stats_area mt-5 mb-5"> <div class="row stats_area mt-5 mb-5">
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Service.Online24}}%</span> <span class="lg_number">{{.Online24}}%</span>
Online last 24 Hours Online last 24 Hours
</div> </div>
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Service.AvgTime}}ms</span> <span class="lg_number">{{.AvgTime}}ms</span>
Average Response Average Response
</div> </div>
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{.Service.AvgUptime}}%</span> <span class="lg_number">{{.AvgUptime}}%</span>
Total Uptime Total Uptime
</div> </div>
</div> </div>
<canvas id="service" width="400" height="120"></canvas> <canvas id="service" width="400" height="120"></canvas>
{{ range .Service.Failures }} {{ range .Failures }}
<blockquote class="blockquote text-right mt-3"> <blockquote class="blockquote text-right mt-3">
<p class="mb-0">{{.ParseError}}</p> <p class="mb-0">{{.ParseError}}</p>
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer> <footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
@ -63,52 +62,53 @@
</div> </div>
{{if .Auth}} {{if Auth}}
<div class="col-12"> <div class="col-12">
<h3>Edit Service</h3> <h3>Edit Service</h3>
<form action="/service/{{.Service.Id}}" method="POST"> <form action="/service/{{.Id}}" method="POST">
<div class="form-group row"> <div class="form-group row">
<label for="inputEmail3" class="col-sm-4 col-form-label">Service Name</label> <label for="inputEmail3" class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="name" class="form-control" value="{{.Service.Name}}" id="inputEmail3"> <input type="text" name="name" class="form-control" value="{{.Name}}" id="inputEmail3">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Application Endpoint (URL)</label> <label for="inputPassword3" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="domain" class="form-control" value="{{.Service.Domain}}" id="inputPassword3"> <input type="text" name="domain" class="form-control" value="{{.Domain}}" id="inputPassword3">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Response (Regex)</label> <label for="inputPassword3" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="expected" class="form-control" value="{{.Service.Expected}}" id="inputPassword3"> <input type="text" name="expected" class="form-control" value="{{.Expected}}" id="inputPassword3">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Status Code</label> <label for="inputPassword3" class="col-sm-4 col-form-label">Expected Status Code</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="number" name="expected_status" class="form-control" value="{{.Service.ExpectedStatus}}" id="inputPassword3"> <input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="inputPassword3">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">HTTP Method</label> <label for="inputPassword3" class="col-sm-4 col-form-label">HTTP Method</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="method" class="form-control" value="{{.Service.Method}}" id="inputPassword3"> <input type="text" name="method" class="form-control" value="{{.Method}}" id="inputPassword3">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Check Interval (Seconds)</label> <label for="inputPassword3" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="number" name="interval" class="form-control" value="{{.Service.Interval}}" id="inputPassword3"> <input type="number" name="interval" class="form-control" value="{{.Interval}}" id="inputPassword3">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<a class="btn btn-primary">Test</a> <a class="btn btn-primary">Test</a>
<button type="submit" class="btn btn-success">Update Service</button> <button type="submit" class="btn btn-success">Update Service</button>
<a href="/service/{{ .Id }}/delete_failures" class="btn btn-danger">Delete All Failures</a>
</div> </div>
</div> </div>
</form> </form>
@ -117,6 +117,13 @@
{{end}} {{end}}
{{ range .Failures }}
<blockquote class="blockquote text-right mt-3">
<p class="mb-0">{{.ParseError}}</p>
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
</blockquote>
{{ end }}
</div> </div>
</div> </div>
@ -129,7 +136,7 @@
data: { data: {
datasets: [{ datasets: [{
label: 'Response Time (Milliseconds)', label: 'Response Time (Milliseconds)',
data: {{js .Service.GraphData}}, data: {{js .GraphData}},
backgroundColor: [ backgroundColor: [
'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)', 'rgba(54, 162, 235, 0.2)',

View File

@ -38,7 +38,7 @@
<td>{{.Name}}</td> <td>{{.Name}}</td>
<td>{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td> <td>{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
<td class="text-right"> <td class="text-right">
<div class="btn-group" data-toggle="buttons"> <div class="btn-group">
<a href="/service/{{.Id}}" class="btn btn-primary">View</a> <a href="/service/{{.Id}}" class="btn btn-primary">View</a>
<a href="/service/{{.Id}}/delete" class="btn btn-danger">Delete</a> <a href="/service/{{.Id}}/delete" class="btn btn-danger">Delete</a>
</div> </div>

View File

@ -15,6 +15,12 @@
<div class="container"> <div class="container">
{{ if .Error }}
<div class="alert alert-danger" role="alert">
{{ .Error }}
</div>
{{ end }}
<form method="POST" action="/setup"> <form method="POST" action="/setup">
<div class="row"> <div class="row">
@ -30,23 +36,23 @@
</div> </div>
<div class="form-group" id="db_host"> <div class="form-group" id="db_host">
<label for="formGroupExampleInput">Host</label> <label for="formGroupExampleInput">Host</label>
<input type="text" name="db_host" class="form-control" value="localhost" placeholder="localhost"> <input type="text" name="db_host" class="form-control" value="{{.DbHost}}" placeholder="localhost">
</div> </div>
<div class="form-group" id="db_port"> <div class="form-group" id="db_port">
<label for="formGroupExampleInput">Database Port</label> <label for="formGroupExampleInput">Database Port</label>
<input type="text" name="db_port" class="form-control" value="5555" placeholder="localhost"> <input type="text" name="db_port" class="form-control" value="{{.DbPort}}" placeholder="localhost">
</div> </div>
<div class="form-group" id="db_user"> <div class="form-group" id="db_user">
<label for="formGroupExampleInput2">Username</label> <label for="formGroupExampleInput2">Username</label>
<input type="text" name="db_user" class="form-control" value="root" placeholder="root"> <input type="text" name="db_user" class="form-control" value="{{.DbUser}}" placeholder="root">
</div> </div>
<div class="form-group" id="db_password"> <div class="form-group" id="db_password">
<label for="formGroupExampleInput2">Password</label> <label for="formGroupExampleInput2">Password</label>
<input type="password" name="db_password" class="form-control" id="formGroupExampleInput2" value="223352hunter" placeholder="password123"> <input type="password" name="db_password" class="form-control" value="{{.DbPass}}" id="formGroupExampleInput2" value="" placeholder="password123">
</div> </div>
<div class="form-group" id="db_database"> <div class="form-group" id="db_database">
<label for="formGroupExampleInput2">Database</label> <label for="formGroupExampleInput2">Database</label>
<input type="text" name="db_database" class="form-control" id="formGroupExampleInput2" value="uptime" placeholder="uptimeapi"> <input type="text" name="db_database" class="form-control" value="{{.DbData}}" id="formGroupExampleInput2" value="statup" placeholder="Database name">
</div> </div>
</div> </div>
@ -55,27 +61,27 @@
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Project Name</label> <label for="formGroupExampleInput">Project Name</label>
<input type="text" name="project" class="form-control" id="formGroupExampleInput" placeholder="Great Uptime"> <input type="text" name="project" class="form-control" value="{{.Project}}" id="formGroupExampleInput" placeholder="Great Uptime">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Project Description</label> <label for="formGroupExampleInput">Project Description</label>
<input type="text" name="description" class="form-control" id="formGroupExampleInput" placeholder="Great Uptime"> <input type="text" name="description" class="form-control" value="{{.Description}}" id="formGroupExampleInput" placeholder="Great Uptime">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Admin Username</label> <label for="formGroupExampleInput">Admin Username</label>
<input type="text" name="username" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin"> <input type="text" name="username" class="form-control" value="{{.Username}}" id="formGroupExampleInput" value="admin" placeholder="admin">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Admin Password</label> <label for="formGroupExampleInput">Admin Password</label>
<input type="password" name="password" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin"> <input type="password" name="password" class="form-control" value="{{.Password}}" id="formGroupExampleInput" value="admin" placeholder="admin">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput">Confirm Password</label> <label for="formGroupExampleInput">Confirm Password</label>
<input type="password" name="confirm_password" class="form-control" id="formGroupExampleInput" value="admin" placeholder="admin"> <input type="password" name="confirm_password" class="form-control" value="{{.Password}}" id="formGroupExampleInput" value="admin" placeholder="admin">
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -27,6 +27,7 @@
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>
<th scope="col">Username</th> <th scope="col">Username</th>
<th scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -34,6 +35,11 @@
<tr> <tr>
<th scope="row">{{.Id}}</th> <th scope="row">{{.Id}}</th>
<td>{{.Username}}</td> <td>{{.Username}}</td>
<td class="text-right">
<div class="btn-group">
<a href="/users/{{.Id}}/delete" class="btn btn-danger">Delete</a>
</div>
</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

119
main.go
View File

@ -1,14 +1,12 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/GeertJohan/go.rice" "github.com/GeertJohan/go.rice"
"github.com/go-yaml/yaml" "github.com/go-yaml/yaml"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/plugin"
_ "github.com/lib/pq"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -20,7 +18,6 @@ import (
) )
var ( var (
db *sql.DB
configs *Config configs *Config
core *Core core *Core
store *sessions.CookieStore store *sessions.CookieStore
@ -93,114 +90,6 @@ func DownloadFile(filepath string, url string) error {
return nil return nil
} }
func SelectSettings(p plugin.Info) map[string]string {
data := make(map[string]string)
var tableInput []string
for _, v := range p.Form {
val := fmt.Sprintf("%v", v.InputName)
tableInput = append(tableInput, val)
}
ins := strings.Join(tableInput, ", ")
sql := fmt.Sprintf("SELECT %v FROM settings_%v LIMIT 1", ins, p.Name)
rows, err := db.Query(sql)
if err != nil {
fmt.Println("SQL ERROR: ", err)
return map[string]string{}
}
count := len(p.Form)
valuePtrs := make([]interface{}, count)
values := make([]interface{}, count)
for rows.Next() {
for i, _ := range p.Form {
valuePtrs[i] = &values[i]
}
err = rows.Scan(valuePtrs...)
if err != nil {
panic(err)
}
for i, col := range p.Form {
val := values[i]
b, _ := val.([]byte)
realVal := string(b)
//col.ChangeVal(realVal)
fmt.Println(col.Value, realVal)
data[col.InputName] = realVal
}
}
return data
}
func CreateSettingsTable(p plugin.Info) string {
var tableValues []string
tableValues = append(tableValues, "plugin text")
tableValues = append(tableValues, "enabled bool")
for _, v := range p.Form {
tb := fmt.Sprintf("%v %v", v.InputName, v.InputType)
tableValues = append(tableValues, tb)
}
vals := strings.Join(tableValues, ", ")
out := fmt.Sprintf("CREATE TABLE settings_%v (%v);", p.Name, vals)
smtp, _ := db.Prepare(out)
_, _ = smtp.Exec()
InitalSettings(p)
return out
}
func InitalSettings(p plugin.Info) {
var tableValues []string
var tableInput []string
tableValues = append(tableValues, "plugin")
tableInput = append(tableInput, fmt.Sprintf("'%v'", p.Name))
tableValues = append(tableValues, "enabled")
tableInput = append(tableInput, "false")
for _, v := range p.Form {
val := fmt.Sprintf("'%v'", v.Value)
tableValues = append(tableValues, v.InputName)
tableInput = append(tableInput, val)
}
vals := strings.Join(tableValues, ",")
ins := strings.Join(tableInput, ",")
sql := fmt.Sprintf("INSERT INTO settings_%v(%v) VALUES(%v);", p.Name, vals, ins)
smtp, _ := db.Prepare(sql)
_, _ = smtp.Exec()
}
func UpdateSettings(p plugin.Info, data map[string]string) {
var tableInput []string
for _, v := range p.Form {
newValue := data[v.InputName]
val := fmt.Sprintf("%v='%v'", v.InputName, newValue)
tableInput = append(tableInput, val)
}
ins := strings.Join(tableInput, ", ")
sql := fmt.Sprintf("UPDATE settings_%v SET %v WHERE plugin='%v';", p.Name, ins, p.Name)
smtp, _ := db.Prepare(sql)
_, _ = smtp.Exec()
}
//func DownloadPlugin(name string) { //func DownloadPlugin(name string) {
// plugin := SelectPlugin(name) // plugin := SelectPlugin(name)
// var _, err = os.Stat("plugins/" + plugin.Namespace) // var _, err = os.Stat("plugins/" + plugin.Namespace)
@ -234,7 +123,6 @@ func UpdateSettings(p plugin.Info, data map[string]string) {
func main() { func main() {
var err error var err error
VERSION = "1.1.1"
fmt.Printf("Starting Statup v%v\n", VERSION) fmt.Printf("Starting Statup v%v\n", VERSION)
RenderBoxes() RenderBoxes()
configs, err = LoadConfig() configs, err = LoadConfig()
@ -270,8 +158,7 @@ func mainProcess() {
} }
func throw(err error) { func throw(err error) {
panic(err) fmt.Println("ERROR: ", err)
fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
@ -317,12 +204,12 @@ func LoadPlugins() {
continue continue
} }
plugActions.OnLoad()
allPlugins = append(allPlugins, plugActions) allPlugins = append(allPlugins, plugActions)
core.Plugins = append(core.Plugins, plugActions.GetInfo()) core.Plugins = append(core.Plugins, plugActions.GetInfo())
} }
OnLoad(dbSession)
fmt.Printf("Loaded %v Plugins\n", len(allPlugins)) fmt.Printf("Loaded %v Plugins\n", len(allPlugins))
ForEachPlugin() ForEachPlugin()

View File

@ -8,7 +8,6 @@ import (
) )
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
VERSION = "1.1.1"
RenderBoxes() RenderBoxes()
os.Remove("./statup.db") os.Remove("./statup.db")
Router() Router()
@ -26,6 +25,7 @@ func TestMySQLMakeConfig(t *testing.T) {
"This is a test of Statup.io!", "This is a test of Statup.io!",
"admin", "admin",
"admin", "admin",
nil,
} }
err := config.Save() err := config.Save()
assert.Nil(t, err) assert.Nil(t, err)
@ -64,6 +64,7 @@ func TestSqliteMakeConfig(t *testing.T) {
"This is a test of Statup.io!", "This is a test of Statup.io!",
"admin", "admin",
"admin", "admin",
nil,
} }
err := config.Save() err := config.Save()
assert.Nil(t, err) assert.Nil(t, err)
@ -93,6 +94,7 @@ func TestPostgresMakeConfig(t *testing.T) {
"This is a test of Statup.io!", "This is a test of Statup.io!",
"admin", "admin",
"admin", "admin",
nil,
} }
err := config.Save() err := config.Save()
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -3,7 +3,6 @@ package plugin
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
"upper.io/db.v3/lib/sqlbuilder" "upper.io/db.v3/lib/sqlbuilder"
) )
@ -14,6 +13,10 @@ import (
// //
// https://statup.io // https://statup.io
// //
//
// An expandable plugin framework that will still
// work even if there's an update or addition.
//
var ( var (
DB sqlbuilder.Database DB sqlbuilder.Database
@ -34,56 +37,22 @@ type PluginInfo struct {
type PluginActions interface { type PluginActions interface {
GetInfo() Info GetInfo() Info
SetInfo(map[string]string) Info SetInfo(map[string]interface{}) Info
Routes() []Routing Routes() []Routing
OnSave(map[string]string) OnSave(map[string]interface{})
OnFailure(*Service) OnFailure(map[string]interface{})
OnSuccess(*Service) OnSuccess(map[string]interface{})
OnSettingsSaved(map[string]string) OnSettingsSaved(map[string]interface{})
OnNewUser(*User) OnNewUser(map[string]interface{})
OnNewService(*Service) OnNewService(map[string]interface{})
OnUpdatedService(*Service) OnUpdatedService(map[string]interface{})
OnDeletedService(*Service) OnDeletedService(map[string]interface{})
OnInstall() OnInstall(map[string]interface{})
OnUninstall() OnUninstall(map[string]interface{})
OnBeforeRequest(map[string]interface{})
OnAfterRequest(map[string]interface{})
OnShutdown() OnShutdown()
OnLoad() OnLoad(sqlbuilder.Database)
OnBeforeRequest()
OnAfterRequest()
}
type User struct {
Id int64
Username string
Password string
Email string
}
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
}
type Failure struct {
Id int
Issue string
Service int64
CreatedAt time.Time
Ago string
} }
type Routing struct { type Routing struct {
@ -95,13 +64,4 @@ type Routing struct {
type Info struct { type Info struct {
Name string Name string
Description string Description string
Form []*FormElement
}
type FormElement struct {
Name string
Description string
InputName string
InputType string
Value string
} }

View File

@ -1,7 +0,0 @@
package plugin
import "testing"
func TestInit(t *testing.T) {
}

Binary file not shown.

View File

@ -4,7 +4,6 @@ import (
"crypto/sha1" "crypto/sha1"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/hunterlong/statup/plugin"
"math/rand" "math/rand"
"strconv" "strconv"
"time" "time"
@ -31,11 +30,10 @@ type Service struct {
AvgResponse string `json:"avg_response"` AvgResponse string `json:"avg_response"`
TotalUptime string `json:"uptime"` TotalUptime string `json:"uptime"`
Failures []*Failure `json:"failures"` Failures []*Failure `json:"failures"`
plugin.Service
} }
func SelectService(id int64) (Service, error) { func SelectService(id int64) (*Service, error) {
var service Service var service *Service
col := dbSession.Collection("services") col := dbSession.Collection("services")
res := col.Find("id", id) res := col.Find("id", id)
err := res.One(&service) err := res.One(&service)
@ -107,8 +105,7 @@ func (s *Service) GraphData() string {
d = append(d, o) d = append(d, o)
} }
data, _ := json.Marshal(d) data, _ := json.Marshal(d)
s.Data = string(data) return string(data)
return s.Data
} }
func (s *Service) AvgUptime() string { func (s *Service) AvgUptime() string {
@ -135,11 +132,22 @@ func (u *Service) Delete() error {
col := dbSession.Collection("services") col := dbSession.Collection("services")
res := col.Find("id", u.Id) res := col.Find("id", u.Id)
err := res.Delete() err := res.Delete()
OnDeletedService(u)
return err return err
} }
func (u *Service) DeleteFailures() {
var fails []*Failure
col := dbSession.Collection("failures")
col.Find("service", u.Id).All(&fails)
for _, fail := range fails {
fail.Delete()
}
}
func (u *Service) Update() { func (u *Service) Update() {
OnUpdateService(u)
} }
func (u *Service) Create() (int64, error) { func (u *Service) Create() (int64, error) {
@ -150,6 +158,7 @@ func (u *Service) Create() (int64, error) {
if uuid == nil { if uuid == nil {
return 0, err return 0, err
} }
OnNewService(u)
return uuid.(int64), err return uuid.(int64), err
} }

View File

@ -20,6 +20,7 @@ type DbConfig struct {
Description string `yaml:"-"` Description string `yaml:"-"`
Username string `yaml:"-"` Username string `yaml:"-"`
Password string `yaml:"-"` Password string `yaml:"-"`
Error error
} }
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
@ -47,24 +48,38 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
description, description,
username, username,
password, password,
nil,
} }
err := config.Save() err := config.Save()
if err != nil { if err != nil {
throw(err) config.Error = err
SetupResponseError(w, r, config)
return
} }
configs, err = LoadConfig() configs, err = LoadConfig()
if err != nil { if err != nil {
throw(err) config.Error = err
SetupResponseError(w, r, config)
return
} }
err = DbConnection(configs.Connection) err = DbConnection(configs.Connection)
if err != nil { if err != nil {
throw(err) DeleteConfig()
config.Error = err
SetupResponseError(w, r, config)
return
} }
admin := &User{
Username: config.Username,
Password: config.Password,
}
admin.Create()
if sample == "on" { if sample == "on" {
LoadSampleData() go LoadSampleData()
} }
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
@ -72,6 +87,21 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
mainProcess() mainProcess()
} }
func DeleteConfig() {
err := os.Remove("./config.yml")
if err != nil {
throw(err)
}
}
type ErrorResponse struct {
Error string
}
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
ExecuteResponse(w, r, "setup.html", a)
}
func (c *DbConfig) Save() error { func (c *DbConfig) Save() error {
var err error var err error
config, err := os.Create("config.yml") config, err := os.Create("config.yml")
@ -102,6 +132,8 @@ func (c *DbConfig) Save() error {
"config.yml", "config.yml",
NewSHA1Hash(5), NewSHA1Hash(5),
NewSHA1Hash(10), NewSHA1Hash(10),
"",
"",
VERSION, VERSION,
[]plugin.Info{}, []plugin.Info{},
[]PluginJSON{}, []PluginJSON{},

View File

@ -4,6 +4,8 @@ CREATE TABLE core (
config VARCHAR(50), config VARCHAR(50),
api_key VARCHAR(50), api_key VARCHAR(50),
api_secret VARCHAR(50), api_secret VARCHAR(50),
style text,
footer text,
version VARCHAR(50) version VARCHAR(50)
); );
CREATE TABLE users ( CREATE TABLE users (

View File

@ -4,6 +4,8 @@ CREATE TABLE core (
config text, config text,
api_key text, api_key text,
api_secret text, api_secret text,
style text,
footer text,
version text version text
); );

View File

@ -4,6 +4,8 @@ CREATE TABLE core (
config text, config text,
api_key text, api_key text,
api_secret text, api_secret text,
style text,
footer text,
version text version text
); );

View File

@ -31,6 +31,12 @@ func SelectUsername(username string) (*User, error) {
return &user, err return &user, err
} }
func (u *User) Delete() error {
col := dbSession.Collection("users")
user := col.Find("id", u.Id)
return user.Delete()
}
func (u *User) Create() (int64, error) { func (u *User) Create() (int64, error) {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
password := HashPassword(u.Password) password := HashPassword(u.Password)
@ -42,6 +48,7 @@ func (u *User) Create() (int64, error) {
if uuid == nil { if uuid == nil {
return 0, err return 0, err
} }
OnNewUser(u)
return uuid.(int64), err return uuid.(int64), err
} }

205
web.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/fatih/structs"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"html/template" "html/template"
@ -16,7 +17,7 @@ var (
) )
const ( const (
cookieKey = "apizer_auth" cookieKey = "statup_auth"
) )
func Router() *mux.Router { func Router() *mux.Router {
@ -31,13 +32,15 @@ func Router() *mux.Router {
r.Handle("/logout", http.HandlerFunc(LogoutHandler)) r.Handle("/logout", http.HandlerFunc(LogoutHandler))
r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET") r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET")
r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST") r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)) r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)).Methods("GET")
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST") r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler)) r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler)) r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler)) r.Handle("/service/{id}/badge.svg", http.HandlerFunc(ServicesBadgeHandler))
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET") r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST") r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
r.Handle("/users/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET") r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET")
r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST") r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST")
r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler)) r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler))
@ -147,12 +150,11 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
} }
func SetupHandler(w http.ResponseWriter, r *http.Request) { func SetupHandler(w http.ResponseWriter, r *http.Request) {
tmpl := Parse("setup.html") ExecuteResponse(w, r, "setup.html", nil)
tmpl.Execute(w, nil)
} }
type index struct { type index struct {
Project string Core Core
Services []*Service Services []*Service
} }
@ -161,10 +163,8 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/setup", http.StatusSeeOther) http.Redirect(w, r, "/setup", http.StatusSeeOther)
return return
} }
out := index{*core, services}
tmpl := Parse("index.html") ExecuteResponse(w, r, "index.html", out)
out := index{core.Name, services}
tmpl.Execute(w, out)
} }
type dashboard struct { type dashboard struct {
@ -178,13 +178,11 @@ type dashboard struct {
func DashboardHandler(w http.ResponseWriter, r *http.Request) { func DashboardHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, cookieKey) session, _ := store.Get(r, cookieKey)
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
tmpl := Parse("login.html") ExecuteResponse(w, r, "login.html", nil)
tmpl.Execute(w, nil)
} else { } else {
tmpl := Parse("dashboard.html")
fails, _ := CountFailures() fails, _ := CountFailures()
out := dashboard{services, core, CountOnline(), len(services), fails} out := dashboard{services, core, CountOnline(), len(services), fails}
tmpl.Execute(w, out) ExecuteResponse(w, r, "dashboard.html", out)
} }
} }
@ -195,18 +193,17 @@ type serviceHandler struct {
} }
func ServicesHandler(w http.ResponseWriter, r *http.Request) { func ServicesHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, cookieKey) auth := IsAuthenticated(r)
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
tmpl := Parse("services.html") ExecuteResponse(w, r, "services.html", services)
tmpl.Execute(w, services)
} }
func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) { func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, cookieKey) auth := IsAuthenticated(r)
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
@ -218,8 +215,28 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/services", http.StatusSeeOther) http.Redirect(w, r, "/services", http.StatusSeeOther)
} }
func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service, _ := SelectService(StringInt(vars["id"]))
service.DeleteFailures()
services, _ = SelectAllServices()
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func IsAuthenticated(r *http.Request) bool { func IsAuthenticated(r *http.Request) bool {
session, _ := store.Get(r, cookieKey) if store == nil {
return false
}
session, err := store.Get(r, cookieKey)
if err != nil {
return false
}
if session.Values["authenticated"] == nil { if session.Values["authenticated"] == nil {
return false return false
} }
@ -233,11 +250,24 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
r.ParseForm() r.ParseForm()
name := r.PostForm.Get("name") name := r.PostForm.Get("project")
description := r.PostForm.Get("description") if name != "" {
core.Name = name core.Name = name
}
description := r.PostForm.Get("description")
if description != core.Description {
core.Description = description core.Description = description
}
style := r.PostForm.Get("style")
if style != core.Style {
core.Style = style
}
footer := r.PostForm.Get("footer")
if footer != core.Footer {
core.Footer = footer
}
core.Update() core.Update()
OnSettingsSaved(core)
http.Redirect(w, r, "/settings", http.StatusSeeOther) http.Redirect(w, r, "/settings", http.StatusSeeOther)
} }
@ -247,27 +277,23 @@ func PluginsHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
tmpl := ParsePlugins("plugins.html")
core.FetchPluginRepo() core.FetchPluginRepo()
var pluginFields []PluginSelect var pluginFields []PluginSelect
for _, p := range allPlugins { for _, p := range allPlugins {
fields := SelectSettings(p.GetInfo()) fields := structs.Map(p.GetInfo())
pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, fields}) pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, fields})
} }
core.PluginFields = pluginFields core.PluginFields = pluginFields
ExecuteResponse(w, r, "plugins.html", core)
fmt.Println(&core.PluginFields)
tmpl.Execute(w, core)
} }
type PluginSelect struct { type PluginSelect struct {
Plugin string Plugin string
Params map[string]string Params map[string]interface{}
} }
func PluginSavedHandler(w http.ResponseWriter, r *http.Request) { func PluginSavedHandler(w http.ResponseWriter, r *http.Request) {
@ -283,8 +309,7 @@ func PluginSavedHandler(w http.ResponseWriter, r *http.Request) {
for k, v := range r.PostForm { for k, v := range r.PostForm {
data[k] = strings.Join(v, "") data[k] = strings.Join(v, "")
} }
UpdateSettings(plug.GetInfo(), data) plug.OnSave(structs.Map(data))
plug.OnSave(data)
http.Redirect(w, r, "/settings", http.StatusSeeOther) http.Redirect(w, r, "/settings", http.StatusSeeOther)
} }
@ -307,8 +332,7 @@ func HelpHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
tmpl := Parse("help.html") ExecuteResponse(w, r, "help.html", nil)
tmpl.Execute(w, nil)
} }
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
@ -338,63 +362,66 @@ func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) { func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r) auth := IsAuthenticated(r)
vars := mux.Vars(r) if !auth {
service, _ := SelectService(StringInt(vars["id"]))
tmpl := Parse("service.html")
serve := &serviceHandler{service, auth}
tmpl.Execute(w, serve)
}
func Parse(file string) *template.Template {
nav, _ := tmplBox.String("nav.html")
render, err := tmplBox.String(file)
if err != nil {
panic(err)
}
t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
"safe": func(html string) template.HTML {
return template.HTML(html)
},
})
t, _ = t.Parse(nav)
t.Parse(render)
return t
}
func ParsePlugins(file string) *template.Template {
nav, _ := tmplBox.String("nav.html")
slack, _ := tmplBox.String("plugins/slack.html")
render, err := tmplBox.String(file)
if err != nil {
panic(err)
}
t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
"safe": func(html string) template.HTML {
return template.HTML(html)
},
})
t, _ = t.Parse(nav)
t.Parse(slack)
t.Parse(render)
return t
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("viewing user")
session, _ := store.Get(r, cookieKey)
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
tmpl := Parse("users.html") vars := mux.Vars(r)
users, _ := SelectAllUsers() service, _ := SelectService(StringInt(vars["id"]))
tmpl.Execute(w, users) ExecuteResponse(w, r, "service.html", service)
}
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
nav, _ := tmplBox.String("nav.html")
render, err := tmplBox.String(file)
if err != nil {
panic(err)
}
t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
"safe": func(html string) template.HTML {
return template.HTML(html)
},
"Auth": func() bool {
return IsAuthenticated(r)
},
"VERSION": func() string {
return VERSION
},
})
t, _ = t.Parse(nav)
t.Parse(render)
t.Execute(w, data)
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
users, _ := SelectAllUsers()
ExecuteResponse(w, r, "users.html", users)
}
func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
user, _ := SelectUser(int64(id))
users, _ := SelectAllUsers()
if len(users) == 1 {
http.Redirect(w, r, "/users", http.StatusSeeOther)
return
}
user.Delete()
http.Redirect(w, r, "/users", http.StatusSeeOther)
} }