upgrades - plugins

pull/10/head
Hunter Long 2018-06-10 20:41:02 -07:00
parent 12bbd2b950
commit dfcdbcffe1
19 changed files with 683 additions and 242 deletions

View File

@ -15,6 +15,7 @@ func DbConnection() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
InitPluginsDatabase()
} }
func UpgradeDatabase() { func UpgradeDatabase() {

21
errors.go Normal file
View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"strings"
)
func (f *Failure) ParseError() string {
err := strings.Contains(f.Issue, "operation timed out")
if err {
return fmt.Sprintf("HTTP Request timed out after x seconds")
}
err = strings.Contains(f.Issue, "x509: certificate is valid")
if err {
return fmt.Sprintf("SSL Certificate invalid")
}
return f.Issue
}

21
hits.go
View File

@ -9,9 +9,26 @@ type Hit struct {
CreatedAt time.Time CreatedAt time.Time
} }
func SelectAllHits(id int64) []Hit { func (s *Service) Hits() []Hit {
var tks []Hit var tks []Hit
rows, err := db.Query("SELECT * FROM hits WHERE service=$1 ORDER BY id DESC LIMIT 256", id) rows, err := db.Query("SELECT * FROM hits WHERE service=$1 ORDER BY id DESC LIMIT 256", s.Id)
if err != nil {
panic(err)
}
for rows.Next() {
var tk Hit
err = rows.Scan(&tk.Id, &tk.Metric, &tk.Value, &tk.CreatedAt)
if err != nil {
panic(err)
}
tks = append(tks, tk)
}
return tks
}
func (s *Service) SelectHitsGroupBy(group string) []Hit {
var tks []Hit
rows, err := db.Query("SELECT date_trunc('$1', created_at), -- or hour, day, week, month, year count(1) FROM hits WHERE service=$2 group by 1", group, s.Id)
if err != nil { if err != nil {
panic(err) panic(err)
} }

7
html/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -14,33 +14,9 @@
<div class="container"> <div class="container">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> {{template "nav"}}
<a class="navbar-brand" href="#">Statup</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<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="/services">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/users">Users</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/plguins">Plugins</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="row stats_area mb-4">
<div class="col-4"> <div class="col-4">
<span class="lg_number">{{ .CountServices }}</span> <span class="lg_number">{{ .CountServices }}</span>
@ -56,8 +32,31 @@
<span class="lg_number">{{ .CountOnline }}</span> <span class="lg_number">{{ .CountOnline }}</span>
Online Services Online Services
</div> </div>
</div>
<div class="row">
<div class="col-12">
<h3>Latest Failures</h3>
{{ range .Services }}
{{$name := .Name}}
{{ range .Failures }}
<blockquote class="blockquote text-left mt-3">
<p class="mb-0">{{$name}}</p>
<p class="mb-0">{{.ParseError}}</p>
<footer class="blockquote-footer">Reported <cite title="Source Title">{{.Ago}}</cite></footer>
</blockquote>
{{ end }}
{{ end }}
</div>
</div> </div>

32
html/tmpl/help.html Normal file
View File

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

View File

@ -27,7 +27,7 @@
<div class="progress-bar {{if .Online24Hours}} bg-success {{else}} bg-danger {{end}}" role="progressbar" style="width: {{.Online24Hours}}%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div> <div class="progress-bar {{if .Online24Hours}} bg-success {{else}} bg-danger {{end}}" role="progressbar" style="width: {{.Online24Hours}}%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
<h3 class="mt-4">{{ .Name }} <h3 class="mt-4"><a href="/service/{{.Id}}">{{ .Name }}</a>
{{if .Online}} {{if .Online}}
<span class="badge online_badge float-right">ONLINE</span> <span class="badge online_badge float-right">ONLINE</span>
{{ else }} {{ else }}
@ -56,7 +56,7 @@
{{ range .Failures }} {{ range .Failures }}
<blockquote class="blockquote text-right mt-3"> <blockquote class="blockquote text-right mt-3">
<p class="mb-0">{{.Issue}}</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>
</blockquote> </blockquote>
{{ end }} {{ end }}

33
html/tmpl/nav.html Normal file
View File

@ -0,0 +1,33 @@
{{define "nav"}}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Statup</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<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="/services">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/users">Users</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/plugins">Plugins</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/settings">Settings</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/help">Help</a>
</li>
</ul>
<span class="navbar-text">
<a class="nav-link" href="/logout">Logout</a>
</span>
</div>
</nav>
{{end}}

View File

@ -7,89 +7,41 @@
<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 | Permissions</title> <title>Statup | Plugins</title>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> {{template "nav"}}
<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="row">
<div class="col-12"> <div class="col-4">
<h3>Users</h3> <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>
<table class="table table-striped"> <a class="nav-link" id="v-pills-profile-tab" data-toggle="pill" href="#v-pills-profile" role="tab" aria-controls="v-pills-profile" aria-selected="false">Slack Integration</a>
<thead> <a class="nav-link" id="v-pills-messages-tab" data-toggle="pill" href="#v-pills-messages" role="tab" aria-controls="v-pills-messages" aria-selected="false">Email</a>
<tr> <a class="nav-link" id="v-pills-settings-tab" data-toggle="pill" href="#v-pills-settings" role="tab" aria-controls="v-pills-settings" aria-selected="false">Settings</a>
<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> </div>
<div class="form-group row"> <div class="col-8">
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label> <div class="tab-content" id="v-pills-tabContent">
<div class="col-sm-10"> <div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">...</div>
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password"> <div class="tab-pane fade" id="v-pills-profile" role="tabpanel" aria-labelledby="v-pills-profile-tab">{{template "slack"}}</div>
<div class="tab-pane fade" id="v-pills-messages" role="tabpanel" aria-labelledby="v-pills-messages-tab">...</div>
<div class="tab-pane fade" id="v-pills-settings" role="tabpanel" aria-labelledby="v-pills-settings-tab">...</div>
</div> </div>
</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>
</div> </div>
<script src="/js/jquery-3.3.1.slim.min.js"></script> <script src="/js/jquery-3.3.1.slim.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,35 @@
{{define "slack"}}
<form action="/plugins/save_slack" method="POST">
<div class="form-group row">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck1">
<label class="custom-control-label" for="customCheck1">Slack Integration Enabled</label>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">API Key</label>
<div class="col-sm-8">
<input type="text" name="domain" class="form-control" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">API Secret</label>
<div class="col-sm-8">
<input type="text" name="expected" class="form-control" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Channel</label>
<div class="col-sm-8">
<input type="number" name="expected_status" class="form-control" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-success">Save</button>
</div>
</div>
</form>
{{end}}

184
html/tmpl/service.html Normal file
View File

@ -0,0 +1,184 @@
<!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>Statup | Services</title>
</head>
<body>
<div class="container">
{{if .Auth}}
{{template "nav"}}
{{end}}
<div class="row">
<div class="col-12">
<div class="col-12 mb-4">
<h3 class="mt-2">{{ .Service.Name }}
{{if .Service.Online}}
<span class="badge online_badge float-right">ONLINE</span>
{{ else }}
<span class="badge offline_badge float-right">OFFLINE</span>
{{end}}</h3>
<div class="row stats_area mt-5 mb-5">
<div class="col-4">
<span class="lg_number">{{.Service.Online24Hours}}%</span>
Online last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">{{.Service.AvgResponse}}ms</span>
Average Response
</div>
<div class="col-4">
<span class="lg_number">{{.Service.TotalUptime}}%</span>
Total Uptime
</div>
</div>
<canvas id="service" width="400" height="120"></canvas>
{{ range .Service.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>
{{if .Auth}}
<div class="col-12">
<h3>Edit Service</h3>
<form action="/service/{{.Service.Id}}" method="POST">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" value="{{.Service.Name}}" id="inputEmail3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
<div class="col-sm-8">
<input type="text" name="domain" class="form-control" value="{{.Service.Domain}}" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
<div class="col-sm-8">
<input type="text" name="expected" class="form-control" value="{{.Service.Expected}}" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Expected Status Code</label>
<div class="col-sm-8">
<input type="number" name="expected_status" class="form-control" value="{{.Service.ExpectedStatus}}" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">HTTP Method</label>
<div class="col-sm-8">
<input type="text" name="method" class="form-control" value="{{.Service.Method}}" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" value="{{.Service.Interval}}" id="inputPassword3">
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<a class="btn btn-primary">Test</a>
<button type="submit" class="btn btn-success">Update Service</button>
</div>
</div>
</form>
</div>
{{end}}
</div>
<script>
var ctx = document.getElementById("service").getContext('2d');
var chartdata = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Response Time (Milliseconds)',
data: {{js .Service.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
},
gridLines: {
display:false
}
}],
xAxes: [{
type: 'time',
distribution: 'series',
gridLines: {
display:false
}
}]
},
elements: {
point: {
radius: 0
}
}
}
});
</script>
<script src="/js/jquery-3.3.1.slim.min.js"></script>
</body>
</html>

View File

@ -14,32 +14,7 @@
<div class="container"> <div class="container">
{{template "nav"}}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Statup</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<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="/services">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/users">Users</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/plguins">Plugins</a>
</li>
</ul>
<span class="navbar-text">
<a class="nav-link" href="/logout">Logout</a>
</span>
</div>
</nav>
<div class="row"> <div class="row">
@ -61,12 +36,11 @@
<tr> <tr>
<th scope="row">{{.Id}}</th> <th scope="row">{{.Id}}</th>
<td>{{.Name}}</td> <td>{{.Name}}</td>
<td>{{.Online}} <span class="badge badge-danger">OFFLINE</span> </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" data-toggle="buttons">
<a href="/services/{{.Id}}" class="btn btn-primary">View</a> <a href="/service/{{.Id}}" class="btn btn-primary">View</a>
<a href="/services/{{.Id}}/edit" class="btn btn-primary">Edit</a> <a href="/service/{{.Id}}/delete" class="btn btn-danger">Delete</a>
<a href="/services/{{.Id}}/delete" class="btn btn-danger">Delete</a>
</div> </div>
</td> </td>
</tr> </tr>
@ -81,7 +55,7 @@
<h3>Create Service</h3> <h3>Create Service</h3>
<form action="/services/create" method="POST"> <form action="/services" 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">
@ -113,7 +87,7 @@
</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</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" id="inputPassword3" placeholder="10"> <input type="number" name="interval" class="form-control" id="inputPassword3" placeholder="10">
</div> </div>
@ -128,7 +102,6 @@
</div> </div>
</div> </div>

34
html/tmpl/settings.html Normal file
View File

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

View File

@ -14,32 +14,7 @@
<div class="container"> <div class="container">
{{template "nav"}}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Statup</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<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="/plguins">Plugins</a>
</li>
</ul>
<span class="navbar-text">
<a class="nav-link" href="/logout">Logout</a>
</span>
</div>
</nav>
<div class="row"> <div class="row">
@ -66,21 +41,27 @@
<h3>Create User</h3> <h3>Create User</h3>
<form action="/users/create" method="POST"> <form action="/users" method="POST">
<div class="form-group row"> <div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label> <label for="inputEmail3" class="col-sm-4 col-form-label">Username</label>
<div class="col-sm-10"> <div class="col-sm-8">
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username"> <input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label> <label for="inputPassword3" class="col-sm-4 col-form-label">Password</label>
<div class="col-sm-10"> <div class="col-sm-8">
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password"> <input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <label for="inputPassword3" class="col-sm-4 col-form-label">Confirm Password</label>
<div class="col-sm-8">
<input type="password" name="password_confirm" class="form-control" id="inputPassword3" placeholder="ConfirmPassword">
</div>
</div>
<div class="form-group row">
<div class="col-sm-8">
<button type="submit" class="btn btn-primary">Create User</button> <button type="submit" class="btn btn-primary">Create User</button>
</div> </div>
</div> </div>

17
plugins.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"github.com/hunterlong/statup/plugins"
)
var (
pluginRoutes []*plugins.Routing
)
func InitPluginsDatabase() {
plugins.InitDB(db)
}
func Routes() []*plugins.Routing {
return plugins.PluginRoutes
}

26
plugins/init.go Normal file
View File

@ -0,0 +1,26 @@
package plugins
import (
"database/sql"
"net/http"
)
var (
db *sql.DB
PluginRoutes []*Routing
)
type Routing struct {
URL string
Method string
Handler func(http.ResponseWriter, *http.Request)
}
func AddRoute(url string, method string, handle func(http.ResponseWriter, *http.Request)) {
route := &Routing{url, method, handle}
PluginRoutes = append(PluginRoutes, route)
}
func InitDB(database *sql.DB) {
db = database
}

30
plugins/slack.go Normal file
View File

@ -0,0 +1,30 @@
package plugins
import "net/http"
func init() {
AddRoute("save_slack", "POST", SaveIt)
AddRoute("create_slack", "GET", Create)
}
func SaveIt(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/plugins", http.StatusSeeOther)
}
func Create(w http.ResponseWriter, r *http.Request) {
CreateTable()
}
func CreateTable() {
sql := "CREATE TABLE slack (enabled BOOLEAN, api_key text, api_key text, channel text);"
db.QueryRow(sql).Scan()
}
func DropTable() {
sql := "DROP TABLE slack;"
db.QueryRow(sql).Scan()
}
func UpdateDatabase() {
}

View File

@ -32,19 +32,13 @@ type Service struct {
Failures []*Failure Failures []*Failure
} }
func SelectService(id string) Service { func SelectService(id string) *Service {
var tk Service for _, s := range services {
rows, err := db.Query("SELECT * FROM services WHERE id=$1", id) if id == strconv.Itoa(int(s.Id)) {
if err != nil { return s
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 return nil
} }
func SelectAllServices() []*Service { func SelectAllServices() []*Service {
@ -109,9 +103,8 @@ type GraphJson struct {
} }
func (s *Service) GraphData() string { func (s *Service) GraphData() string {
hits := SelectAllHits(s.Id)
var d []*GraphJson var d []*GraphJson
for _, h := range hits { for _, h := range s.Hits() {
val := h.CreatedAt val := h.CreatedAt
o := &GraphJson{ o := &GraphJson{
X: val.String(), X: val.String(),
@ -144,6 +137,18 @@ func (s *Service) AvgUptime() string {
return s.TotalUptime return s.TotalUptime
} }
func (u *Service) Delete() {
stmt, err := db.Prepare("DELETE FROM services WHERE id=$1")
if err != nil {
panic(err)
}
stmt.Exec(u.Id)
}
func (u *Service) Update() {
}
func (u *Service) Create() int { func (u *Service) Create() int {
var lastInsertId int var lastInsertId int
err := db.QueryRow("INSERT INTO services(name, domain, method, port, expected, expected_status, interval, created_at) VALUES($1,$2,$3,$4,$5,$6,$7,NOW()) returning id;", u.Name, u.Domain, u.Method, u.Port, u.Expected, u.ExpectedStatus, u.Interval).Scan(&lastInsertId) err := db.QueryRow("INSERT INTO services(name, domain, method, port, expected, expected_status, interval, created_at) VALUES($1,$2,$3,$4,$5,$6,$7,NOW()) returning id;", u.Name, u.Domain, u.Method, u.Port, u.Expected, u.ExpectedStatus, u.Interval).Scan(&lastInsertId)

228
web.go
View File

@ -2,29 +2,57 @@ package main
import ( import (
"fmt" "fmt"
"github.com/gorilla/mux"
"html/template" "html/template"
"net/http" "net/http"
"strconv" "strconv"
"time"
) )
func RunHTTPServer() { func RunHTTPServer() {
fmt.Println("Fusioner HTTP Server running on http://localhost:8080")
css := http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox())) r := mux.NewRouter()
js := http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox()))
http.Handle("/", http.HandlerFunc(IndexHandler)) fmt.Println("Statup HTTP Server running on http://localhost:8080")
http.Handle("/css/", css) r.Handle("/", http.HandlerFunc(IndexHandler))
http.Handle("/js/", js)
http.Handle("/setup", http.HandlerFunc(SetupHandler)) r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox())))
http.Handle("/setup/save", http.HandlerFunc(ProcessSetupHandler)) r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox())))
http.Handle("/dashboard", http.HandlerFunc(DashboardHandler))
http.Handle("/login", http.HandlerFunc(LoginHandler)) r.Handle("/setup", http.HandlerFunc(SetupHandler))
http.Handle("/logout", http.HandlerFunc(LogoutHandler)) r.Handle("/setup/save", http.HandlerFunc(ProcessSetupHandler))
//http.Handle("/auth", http.HandlerFunc(AuthenticateHandler)) r.Handle("/dashboard", http.HandlerFunc(DashboardHandler))
http.Handle("/users/create", http.HandlerFunc(CreateUserHandler)) r.Handle("/login", http.HandlerFunc(LoginHandler))
http.Handle("/services/create", http.HandlerFunc(CreateServiceHandler)) r.Handle("/logout", http.HandlerFunc(LogoutHandler))
http.Handle("/services", http.HandlerFunc(ServicesHandler))
http.Handle("/users", http.HandlerFunc(UsersHandler)) r.Handle("/services", http.HandlerFunc(ServicesHandler))
http.ListenAndServe(":8080", nil) r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
r.Handle("/users", http.HandlerFunc(UsersHandler))
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
r.Handle("/settings", http.HandlerFunc(SettingsHandler))
r.Handle("/plugins", http.HandlerFunc(PluginsHandler))
r.Handle("/help", http.HandlerFunc(HelpHandler))
for _, route := range Routes() {
fmt.Printf("Adding plugin route: /plugins/%v\n", route.URL)
r.Handle("/plugins/"+route.URL, http.HandlerFunc(route.Handler)).Methods(route.Method)
}
srv := &http.Server{
Addr: "0.0.0.0:8080",
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r,
}
srv.ListenAndServe()
} }
func LogoutHandler(w http.ResponseWriter, r *http.Request) { func LogoutHandler(w http.ResponseWriter, r *http.Request) {
@ -107,15 +135,8 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
} }
func SetupHandler(w http.ResponseWriter, r *http.Request) { func SetupHandler(w http.ResponseWriter, r *http.Request) {
setupFile, err := tmplBox.String("setup.html") tmpl := Parse("setup.html")
if err != nil { tmpl.Execute(w, nil)
panic(err)
}
setupTmpl, err := template.New("message").Parse(setupFile)
if err != nil {
panic(err)
}
setupTmpl.Execute(w, nil)
} }
type index struct { type index struct {
@ -129,18 +150,9 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
indexFile, err := tmplBox.String("index.html") tmpl := Parse("index.html")
if err != nil {
panic(err)
}
indexTmpl, err := template.New("message").Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
}).Parse(indexFile)
out := index{core.Name, services} out := index{core.Name, services}
indexTmpl.Execute(w, out) tmpl.Execute(w, out)
} }
type dashboard struct { type dashboard struct {
@ -154,45 +166,134 @@ type dashboard struct {
func DashboardHandler(w http.ResponseWriter, r *http.Request) { func DashboardHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "apizer_auth") session, _ := store.Get(r, "apizer_auth")
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
loginFile, err := tmplBox.String("login.html") tmpl := Parse("login.html")
if err != nil { tmpl.Execute(w, nil)
panic(err)
}
loginTmpl, err := template.New("message").Parse(loginFile)
if err != nil {
panic(err)
}
loginTmpl.Execute(w, nil)
} else { } else {
dashboardFile, err := tmplBox.String("dashboard.html") tmpl := Parse("dashboard.html")
if err != nil {
panic(err)
}
dashboardTmpl, err := template.New("message").Parse(dashboardFile)
if err != nil {
panic(err)
}
out := dashboard{services, core, CountOnline(), len(services), CountFailures()} out := dashboard{services, core, CountOnline(), len(services), CountFailures()}
dashboardTmpl.Execute(w, out) tmpl.Execute(w, out)
} }
} }
type serviceHandler struct {
Service *Service
Auth bool
}
func ServicesHandler(w http.ResponseWriter, r *http.Request) { func ServicesHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "apizer_auth") session, _ := store.Get(r, "apizer_auth")
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
tokensFile, err := tmplBox.String("services.html") tmpl := Parse("services.html")
tmpl.Execute(w, services)
}
func ServicesDeleteHandler(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
}
vars := mux.Vars(r)
service := SelectService(vars["id"])
service.Delete()
services = SelectAllServices()
http.Redirect(w, r, "/services", http.StatusSeeOther)
}
func IsAuthenticated(r *http.Request) bool {
session, _ := store.Get(r, "apizer_auth")
return session.Values["authenticated"].(bool)
}
func SettingsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
tmpl := Parse("settings.html")
tmpl.Execute(w, core)
}
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
tmpl := ParsePlugins("plugins.html")
tmpl.Execute(w, core)
}
func HelpHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
tmpl := Parse("help.html")
tmpl.Execute(w, nil)
}
func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
//auth := IsAuthenticated(r)
//
//vars := mux.Vars(r)
//service := SelectService(vars["id"])
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
vars := mux.Vars(r)
service := SelectService(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 { if err != nil {
panic(err) panic(err)
} }
tokensTmpl, err := template.New("message").Parse(tokensFile) t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(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 { if err != nil {
panic(err) panic(err)
} }
tokensTmpl.Execute(w, services) t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
})
t, _ = t.Parse(nav)
t.Parse(slack)
t.Parse(render)
return t
} }
func UsersHandler(w http.ResponseWriter, r *http.Request) { func UsersHandler(w http.ResponseWriter, r *http.Request) {
@ -201,15 +302,8 @@ func UsersHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
usersFile, err := tmplBox.String("users.html") tmpl := Parse("users.html")
if err != nil { tmpl.Execute(w, SelectAllUsers())
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) { func PermissionsHandler(w http.ResponseWriter, r *http.Request) {