mirror of https://github.com/statping/statping
upgrades - plugins
parent
12bbd2b950
commit
dfcdbcffe1
|
@ -15,6 +15,7 @@ func DbConnection() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
InitPluginsDatabase()
|
||||
}
|
||||
|
||||
func UpgradeDatabase() {
|
||||
|
|
|
@ -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
21
hits.go
|
@ -9,9 +9,26 @@ type Hit struct {
|
|||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func SelectAllHits(id int64) []Hit {
|
||||
func (s *Service) Hits() []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 {
|
||||
panic(err)
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -14,33 +14,9 @@
|
|||
|
||||
<div class="container">
|
||||
|
||||
<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="/plguins">Plugins</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
{{template "nav"}}
|
||||
|
||||
<div class="row stats_area">
|
||||
<div class="row stats_area mb-4">
|
||||
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{ .CountServices }}</span>
|
||||
|
@ -56,10 +32,33 @@
|
|||
<span class="lg_number">{{ .CountOnline }}</span>
|
||||
Online Services
|
||||
</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>
|
||||
|
||||
<script src="/js/jquery-3.3.1.slim.min.js"></script>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
||||
<h3 class="mt-4">{{ .Name }}
|
||||
<h3 class="mt-4"><a href="/service/{{.Id}}">{{ .Name }}</a>
|
||||
{{if .Online}}
|
||||
<span class="badge online_badge float-right">ONLINE</span>
|
||||
{{ else }}
|
||||
|
@ -56,7 +56,7 @@
|
|||
|
||||
{{ range .Failures }}
|
||||
<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>
|
||||
</blockquote>
|
||||
{{ end }}
|
||||
|
|
|
@ -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}}
|
|
@ -7,89 +7,41 @@
|
|||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/base.css">
|
||||
|
||||
<title>Statup | Permissions</title>
|
||||
<title>Statup | Plugins</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>
|
||||
{{template "nav"}}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="col-4">
|
||||
|
||||
<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 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" 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>
|
||||
<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>
|
||||
<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>
|
||||
</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 class="col-8">
|
||||
<div class="tab-content" id="v-pills-tabContent">
|
||||
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">...</div>
|
||||
<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 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>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -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}}
|
|
@ -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>
|
|
@ -14,32 +14,7 @@
|
|||
|
||||
<div class="container">
|
||||
|
||||
|
||||
<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>
|
||||
{{template "nav"}}
|
||||
|
||||
<div class="row">
|
||||
|
||||
|
@ -61,12 +36,11 @@
|
|||
<tr>
|
||||
<th scope="row">{{.Id}}</th>
|
||||
<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">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<a href="/services/{{.Id}}" class="btn btn-primary">View</a>
|
||||
<a href="/services/{{.Id}}/edit" class="btn btn-primary">Edit</a>
|
||||
<a href="/services/{{.Id}}/delete" class="btn btn-danger">Delete</a>
|
||||
<a href="/service/{{.Id}}" class="btn btn-primary">View</a>
|
||||
<a href="/service/{{.Id}}/delete" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -81,7 +55,7 @@
|
|||
|
||||
<h3>Create Service</h3>
|
||||
|
||||
<form action="/services/create" method="POST">
|
||||
<form action="/services" 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">
|
||||
|
@ -113,7 +87,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input type="number" name="interval" class="form-control" id="inputPassword3" placeholder="10">
|
||||
</div>
|
||||
|
@ -128,7 +102,6 @@
|
|||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -14,32 +14,7 @@
|
|||
|
||||
<div class="container">
|
||||
|
||||
|
||||
<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>
|
||||
{{template "nav"}}
|
||||
|
||||
<div class="row">
|
||||
|
||||
|
@ -66,21 +41,27 @@
|
|||
|
||||
<h3>Create User</h3>
|
||||
|
||||
<form action="/users/create" method="POST">
|
||||
<form action="/users" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="inputEmail3" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<label for="inputEmail3" class="col-sm-4 col-form-label">Username</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<label for="inputPassword3" class="col-sm-4 col-form-label">Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
31
services.go
31
services.go
|
@ -32,19 +32,13 @@ type Service struct {
|
|||
Failures []*Failure
|
||||
}
|
||||
|
||||
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)
|
||||
func SelectService(id string) *Service {
|
||||
for _, s := range services {
|
||||
if id == strconv.Itoa(int(s.Id)) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return tk
|
||||
return nil
|
||||
}
|
||||
|
||||
func SelectAllServices() []*Service {
|
||||
|
@ -109,9 +103,8 @@ type GraphJson struct {
|
|||
}
|
||||
|
||||
func (s *Service) GraphData() string {
|
||||
hits := SelectAllHits(s.Id)
|
||||
var d []*GraphJson
|
||||
for _, h := range hits {
|
||||
for _, h := range s.Hits() {
|
||||
val := h.CreatedAt
|
||||
o := &GraphJson{
|
||||
X: val.String(),
|
||||
|
@ -144,6 +137,18 @@ func (s *Service) AvgUptime() string {
|
|||
return s.TotalUptime
|
||||
}
|
||||
|
||||
func (u *Service) Delete() {
|
||||
stmt, err := db.Prepare("DELETE FROM services WHERE id=$1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stmt.Exec(u.Id)
|
||||
}
|
||||
|
||||
func (u *Service) Update() {
|
||||
|
||||
}
|
||||
|
||||
func (u *Service) Create() int {
|
||||
var lastInsertId int
|
||||
err := db.QueryRow("INSERT INTO services(name, domain, method, port, expected, expected_status, interval, created_at) VALUES($1,$2,$3,$4,$5,$6,$7,NOW()) returning id;", u.Name, u.Domain, u.Method, u.Port, u.Expected, u.ExpectedStatus, u.Interval).Scan(&lastInsertId)
|
||||
|
|
228
web.go
228
web.go
|
@ -2,29 +2,57 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RunHTTPServer() {
|
||||
fmt.Println("Fusioner HTTP Server running on http://localhost:8080")
|
||||
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("/users/create", http.HandlerFunc(CreateUserHandler))
|
||||
http.Handle("/services/create", http.HandlerFunc(CreateServiceHandler))
|
||||
http.Handle("/services", http.HandlerFunc(ServicesHandler))
|
||||
http.Handle("/users", http.HandlerFunc(UsersHandler))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
||||
fmt.Println("Statup HTTP Server running on http://localhost:8080")
|
||||
r.Handle("/", http.HandlerFunc(IndexHandler))
|
||||
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(cssBox.HTTPBox())))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(jsBox.HTTPBox())))
|
||||
|
||||
r.Handle("/setup", http.HandlerFunc(SetupHandler))
|
||||
r.Handle("/setup/save", http.HandlerFunc(ProcessSetupHandler))
|
||||
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler))
|
||||
r.Handle("/login", http.HandlerFunc(LoginHandler))
|
||||
r.Handle("/logout", http.HandlerFunc(LogoutHandler))
|
||||
|
||||
r.Handle("/services", http.HandlerFunc(ServicesHandler))
|
||||
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) {
|
||||
|
@ -107,15 +135,8 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
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)
|
||||
tmpl := Parse("setup.html")
|
||||
tmpl.Execute(w, nil)
|
||||
}
|
||||
|
||||
type index struct {
|
||||
|
@ -129,18 +150,9 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
indexFile, err := tmplBox.String("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)
|
||||
tmpl := Parse("index.html")
|
||||
out := index{core.Name, services}
|
||||
indexTmpl.Execute(w, out)
|
||||
tmpl.Execute(w, out)
|
||||
}
|
||||
|
||||
type dashboard struct {
|
||||
|
@ -154,45 +166,134 @@ type dashboard struct {
|
|||
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := store.Get(r, "apizer_auth")
|
||||
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
|
||||
loginFile, err := tmplBox.String("login.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loginTmpl, err := template.New("message").Parse(loginFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loginTmpl.Execute(w, nil)
|
||||
tmpl := Parse("login.html")
|
||||
tmpl.Execute(w, nil)
|
||||
} else {
|
||||
dashboardFile, err := tmplBox.String("dashboard.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dashboardTmpl, err := template.New("message").Parse(dashboardFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tmpl := Parse("dashboard.html")
|
||||
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) {
|
||||
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")
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
|
@ -201,15 +302,8 @@ func UsersHandler(w http.ResponseWriter, r *http.Request) {
|
|||
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())
|
||||
tmpl := Parse("users.html")
|
||||
tmpl.Execute(w, SelectAllUsers())
|
||||
}
|
||||
|
||||
func PermissionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
Loading…
Reference in New Issue