mirror of
https://github.com/Luzifer/mondash.git
synced 2024-12-23 04:21:18 +00:00
Initial version
This commit is contained in:
commit
919e361353
8 changed files with 616 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
gin-bin
|
30
README.md
Normal file
30
README.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Luzifer / mondash
|
||||||
|
|
||||||
|
MonDash is a service for everyone having to display monitoring results to people who have not
|
||||||
|
the time or knowledge to get familar with Nagios / Icinga or similar monitoring systems. Therefore
|
||||||
|
MonDash provides a simple API to submit monitoring results and a simple dashboard to view those
|
||||||
|
results.
|
||||||
|
|
||||||
|
## Hosted
|
||||||
|
|
||||||
|
There is an instance of MonDash running on [mondash.org](https://mondash.org/) you can use for free. This means you can just head over there, create your own dashboard with one click and start to push your own metrics to your dashboard within 5 minutes. No registration, no fees, just your dashboard and you.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
However maybe you want to use MonDash for data you don't like to have public visible on the internet. As it is open source you also can host your own instance: The most simple way to install your own instance is to download a binary distribution on [gobuild.luzifer.io](http://gobuild.luzifer.io/github.com/Luzifer/mondash).
|
||||||
|
|
||||||
|
This archive will contain the binary you need to run your own instance and the template used for the display. If you want just edit the template, restart the daemon and you're done customizing MonDash. If you do so please do me one small favor: Include a hint to this repository / my instance.
|
||||||
|
|
||||||
|
MonDash needs some environment variables set when running:
|
||||||
|
|
||||||
|
+ `AWS_ACCESS_KEY_ID` - Your AWS Access-Key with access to the `S3Bucket`
|
||||||
|
+ `AWS_SECRET_ACCESS_KEY` - Your AWS Secret-Access-Key with access to the `S3Bucket`
|
||||||
|
+ `S3Bucket` - The S3 bucket used to store the dashboard metrics
|
||||||
|
+ `BASE_URL` - The Base-URL the application is running on for example `https://www.mondash.org`
|
||||||
|
+ `API_TOKEN` - API Token used for the /welcome dashboard (you can choose your own)
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Just some words regarding security: MonDash was designed to be an open platform for creating dashboards without any hazzle. You just open a dashboard, send some data to it and you're already done. No need to think about OAuth or other authentication mechanisms.
|
||||||
|
|
||||||
|
The downpath of that concept is of course everyone can access every dashboard and see every data placed on it. So please don't use the public instances for private and/or secret data. You can just set up your own instance within 5 minutes (okay maybe 10 minutes if you want to do it right) and you can ensure that this instance is hidden from the internet.
|
97
apiary.apib
Normal file
97
apiary.apib
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
FORMAT: 1A
|
||||||
|
HOST: www.mondash.org
|
||||||
|
|
||||||
|
# MonDash
|
||||||
|
MonDash is a service for everyone having to display monitoring results to people who have not
|
||||||
|
the time or knowledge to get familar with Nagios / Icinga or similar monitoring systems. Therefore
|
||||||
|
MonDash provides a simple API to submit monitoring results and a simple dashboard to view those
|
||||||
|
results.
|
||||||
|
|
||||||
|
For the API to work you will need the APIToken assigned to your dashboard. This token is displayed
|
||||||
|
on the dashboard itself as long as no metrics are available to display.
|
||||||
|
|
||||||
|
To start just create a [randomly named dashboard](http://www.mondash.org/create) or start with a
|
||||||
|
named dashboard by simply visiting http://www.mondash.org/mydashboardname (if you plan to use the
|
||||||
|
named version please pay attention this will be easily guessable and you data is lesser protected
|
||||||
|
than with the random naming.
|
||||||
|
|
||||||
|
## Dashboard [/{dashid}]
|
||||||
|
|
||||||
|
This API controls your dashboard itself
|
||||||
|
|
||||||
|
+ Parameters
|
||||||
|
+ dashid (required, string, `098f6bcd4621d373cade`) ... The id of your dashboard to be found in the URL
|
||||||
|
|
||||||
|
### Delete your dashboard [DELETE]
|
||||||
|
|
||||||
|
This request will delete all your monitoring results available on your dashboard and release the
|
||||||
|
dashboard URL to the public.
|
||||||
|
|
||||||
|
_Please pay attention your dashboard URL will be available for others to register immediately
|
||||||
|
as we are not storing any data beyond this DELETE request._
|
||||||
|
|
||||||
|
+ Request
|
||||||
|
|
||||||
|
+ Header
|
||||||
|
|
||||||
|
Authorization: MyAPIToken
|
||||||
|
|
||||||
|
+ Response 200 (text/plain)
|
||||||
|
|
||||||
|
OK
|
||||||
|
|
||||||
|
## Metric [/{dashid}/{metricid}]
|
||||||
|
|
||||||
|
This API controls the metrics on your dashboard
|
||||||
|
|
||||||
|
+ Parameters
|
||||||
|
+ dashid (required, string, `098f6bcd4621d373cade`) ... The id of your dashboard to be found in the URL
|
||||||
|
+ metricid (required, string, `beer_available`) ... The unique name for your metric
|
||||||
|
|
||||||
|
### Submit a monitoring result [PUT]
|
||||||
|
+ Request (application/json)
|
||||||
|
|
||||||
|
+ Header
|
||||||
|
|
||||||
|
Authorization: MyAPIToken
|
||||||
|
|
||||||
|
+ Body
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "Amount of beer in the fridge",
|
||||||
|
"description": "Currently there are 12 bottles of beer in the fridge",
|
||||||
|
"status": "OK",
|
||||||
|
"expires": 604800,
|
||||||
|
"freshness": 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
+ Attributes (object)
|
||||||
|
+ title (required, string) - The title of the metric to display on the dashboard
|
||||||
|
+ description (required, string) - A descriptive text for the current state of the metric
|
||||||
|
+ status (required, enum[string])
|
||||||
|
+ `OK`
|
||||||
|
+ `Warning`
|
||||||
|
+ `Critical`
|
||||||
|
+ `Unknown`
|
||||||
|
+ expires: 604800 (optional, number) - Time in seconds when to remove the metric if there is no update (Valid: `0 < x < 604800`)
|
||||||
|
+ freshness: 3600 (optional, number) - Time in seconds when to switch to `Unkown` state of there is no update (Valid: `0 < x < 604800`)
|
||||||
|
|
||||||
|
+ Response 200 (text/plain)
|
||||||
|
|
||||||
|
+ Body
|
||||||
|
|
||||||
|
OK
|
||||||
|
|
||||||
|
### Delete a metric from your dashboard [DELETE]
|
||||||
|
|
||||||
|
+ Request
|
||||||
|
|
||||||
|
+ Header
|
||||||
|
|
||||||
|
Authorization: MyAPIToken
|
||||||
|
|
||||||
|
+ Response 200 (text/plain)
|
||||||
|
|
||||||
|
+ Body
|
||||||
|
|
||||||
|
OK
|
184
main.go
Normal file
184
main.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"launchpad.net/goamz/aws"
|
||||||
|
"launchpad.net/goamz/s3"
|
||||||
|
|
||||||
|
"github.com/flosch/pongo2"
|
||||||
|
"github.com/go-martini/martini"
|
||||||
|
|
||||||
|
_ "github.com/flosch/pongo2-addons"
|
||||||
|
)
|
||||||
|
|
||||||
|
var templates = make(map[string]*pongo2.Template)
|
||||||
|
var s3Storage *s3.Bucket
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
preloadTemplates()
|
||||||
|
|
||||||
|
// Initialize S3 storage
|
||||||
|
awsAuth, err := aws.EnvAuth()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
s3Conn := s3.New(awsAuth, aws.EUWest)
|
||||||
|
s3Storage = s3Conn.Bucket(os.Getenv("S3Bucket"))
|
||||||
|
|
||||||
|
m := martini.Classic()
|
||||||
|
|
||||||
|
// Assets are in assets folder
|
||||||
|
m.Use(martini.Static("assets", martini.StaticOptions{Prefix: "/assets"}))
|
||||||
|
|
||||||
|
// Real handlers
|
||||||
|
m.Get("/", func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
http.Redirect(res, req, "/welcome", 302)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Get("/create", func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
urlProposal := generateAPIKey()[0:20]
|
||||||
|
_, err := s3Storage.Get(urlProposal)
|
||||||
|
for err == nil {
|
||||||
|
urlProposal = generateAPIKey()[0:20]
|
||||||
|
_, err = s3Storage.Get(urlProposal)
|
||||||
|
}
|
||||||
|
http.Redirect(res, req, fmt.Sprintf("/%s", urlProposal), http.StatusTemporaryRedirect)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Get("/:dashid", func(params martini.Params, res http.ResponseWriter) {
|
||||||
|
dash, err := LoadDashboard(params["dashid"])
|
||||||
|
if err != nil {
|
||||||
|
dash = &Dashboard{APIKey: generateAPIKey(), Metrics: DashboardMetrics{}}
|
||||||
|
}
|
||||||
|
sort.Sort(sort.Reverse(DashboardMetrics(dash.Metrics)))
|
||||||
|
renderTemplate("dashboard.html", pongo2.Context{
|
||||||
|
"dashid": params["dashid"],
|
||||||
|
"metrics": dash.Metrics,
|
||||||
|
"apikey": dash.APIKey,
|
||||||
|
"baseurl": os.Getenv("BASE_URL"),
|
||||||
|
}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Delete("/:dashid", func(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
||||||
|
dash, err := LoadDashboard(params["dashid"])
|
||||||
|
if err != nil {
|
||||||
|
http.Error(res, "This dashboard does not exist.", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dash.APIKey != req.Header.Get("Authorization") {
|
||||||
|
http.Error(res, "APIKey did not match.", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s3Storage.Del(params["dashid"])
|
||||||
|
http.Error(res, "OK", http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Put("/:dashid/:metricid", func(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
||||||
|
body, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(res, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metricUpdate := NewDashboardMetric()
|
||||||
|
err = json.Unmarshal(body, metricUpdate)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(res, "Unable to unmarshal json", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dash, err := LoadDashboard(params["dashid"])
|
||||||
|
if err != nil {
|
||||||
|
dash = &Dashboard{APIKey: req.Header.Get("Authorization"), Metrics: DashboardMetrics{}, DashboardID: params["dashid"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dash.APIKey != req.Header.Get("Authorization") {
|
||||||
|
http.Error(res, "APIKey did not match.", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := false
|
||||||
|
for _, m := range dash.Metrics {
|
||||||
|
if m.MetricID == params["metricid"] {
|
||||||
|
m.Update(metricUpdate)
|
||||||
|
updated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
tmp := &DashboardMetric{MetricID: params["metricid"]}
|
||||||
|
tmp.Update(metricUpdate)
|
||||||
|
dash.Metrics = append(dash.Metrics, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
dash.Save()
|
||||||
|
|
||||||
|
http.Error(res, "OK", http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Delete("/:dashid/:metricid", func(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
||||||
|
dash, err := LoadDashboard(params["dashid"])
|
||||||
|
if err != nil {
|
||||||
|
dash = &Dashboard{APIKey: req.Header.Get("Authorization"), Metrics: DashboardMetrics{}, DashboardID: params["dashid"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dash.APIKey != req.Header.Get("Authorization") {
|
||||||
|
http.Error(res, "APIKey did not match.", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := DashboardMetrics{}
|
||||||
|
for _, m := range dash.Metrics {
|
||||||
|
if m.MetricID != params["metricid"] {
|
||||||
|
tmp = append(tmp, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dash.Metrics = tmp
|
||||||
|
dash.Save()
|
||||||
|
|
||||||
|
http.Error(res, "OK", http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
go RunWelcomePage()
|
||||||
|
|
||||||
|
// GO!
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateAPIKey() string {
|
||||||
|
t := time.Now().String()
|
||||||
|
sum := md5.Sum([]byte(t))
|
||||||
|
return fmt.Sprintf("%x", sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(templateName string, context pongo2.Context, res http.ResponseWriter) {
|
||||||
|
if tpl, ok := templates[templateName]; ok {
|
||||||
|
tpl.ExecuteWriter(context, res)
|
||||||
|
} else {
|
||||||
|
res.WriteHeader(http.StatusInternalServerError)
|
||||||
|
res.Write([]byte(fmt.Sprintf("Template %s not found!", templateName)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func preloadTemplates() {
|
||||||
|
templateNames, err := ioutil.ReadDir("templates")
|
||||||
|
if err != nil {
|
||||||
|
panic("Templates directory not available!")
|
||||||
|
}
|
||||||
|
for _, tplname := range templateNames {
|
||||||
|
templates[tplname.Name()] = pongo2.Must(pongo2.FromFile(fmt.Sprintf("templates/%s", tplname.Name())))
|
||||||
|
}
|
||||||
|
}
|
10
strings.go
Normal file
10
strings.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
func stringInSlice(a string, list []string) bool {
|
||||||
|
for _, b := range list {
|
||||||
|
if b == a {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
139
structs.go
Normal file
139
structs.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"launchpad.net/goamz/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dashboard struct {
|
||||||
|
DashboardID string `json:"-"`
|
||||||
|
APIKey string `json:"api_key"`
|
||||||
|
Metrics DashboardMetrics `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadDashboard(dashid string) (*Dashboard, error) {
|
||||||
|
data, err := s3Storage.Get(dashid)
|
||||||
|
if err != nil {
|
||||||
|
return &Dashboard{}, errors.New("Dashboard not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := &Dashboard{DashboardID: dashid}
|
||||||
|
json.Unmarshal(data, tmp)
|
||||||
|
|
||||||
|
return tmp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dashboard) Save() {
|
||||||
|
data, err := json.Marshal(d)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
err = s3Storage.Put(d.DashboardID, data, "application/json", s3.Private)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardMetrics []*DashboardMetric
|
||||||
|
|
||||||
|
func (a DashboardMetrics) Len() int { return len(a) }
|
||||||
|
func (a DashboardMetrics) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a DashboardMetrics) Less(i, j int) bool {
|
||||||
|
return a[i].HistoricalData[0].Time.Before(a[j].HistoricalData[0].Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardMetric struct {
|
||||||
|
MetricID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Expires int64 `json:"expires,omitifempty"`
|
||||||
|
Freshness int64 `json:"freshness,omitifempty"`
|
||||||
|
HistoricalData DashboardMetricHistory `json:"history,omitifempty"`
|
||||||
|
Meta DashboardMetricMeta `json:"meta,omitifempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardMetricStatus struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardMetricMeta struct {
|
||||||
|
LastUpdate time.Time
|
||||||
|
LastOK time.Time
|
||||||
|
PercOK float64
|
||||||
|
PercWarn float64
|
||||||
|
PercCrit float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardMetricHistory []DashboardMetricStatus
|
||||||
|
|
||||||
|
func NewDashboardMetric() *DashboardMetric {
|
||||||
|
return &DashboardMetric{
|
||||||
|
Status: "Unknown",
|
||||||
|
Expires: 604800,
|
||||||
|
Freshness: 3600,
|
||||||
|
HistoricalData: DashboardMetricHistory{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DashboardMetric) Update(m *DashboardMetric) {
|
||||||
|
dm.Title = m.Title
|
||||||
|
dm.Description = m.Description
|
||||||
|
dm.Status = m.Status
|
||||||
|
if m.Expires != 0 {
|
||||||
|
dm.Expires = m.Expires
|
||||||
|
}
|
||||||
|
if m.Freshness != 0 {
|
||||||
|
dm.Freshness = m.Freshness
|
||||||
|
}
|
||||||
|
dm.HistoricalData = append(DashboardMetricHistory{DashboardMetricStatus{
|
||||||
|
Time: time.Now(),
|
||||||
|
Status: m.Status,
|
||||||
|
}}, dm.HistoricalData...)
|
||||||
|
|
||||||
|
countStatus := make(map[string]float64)
|
||||||
|
|
||||||
|
expired := time.Now().Add(time.Duration(dm.Expires*-1) * time.Second)
|
||||||
|
tmp := DashboardMetricHistory{}
|
||||||
|
for _, s := range dm.HistoricalData {
|
||||||
|
if s.Time.After(expired) {
|
||||||
|
tmp = append(tmp, s)
|
||||||
|
countStatus[s.Status] = countStatus[s.Status] + 1
|
||||||
|
countStatus["Total"] = countStatus["Total"] + 1
|
||||||
|
if dm.Meta.LastOK.Before(s.Time) && s.Status == "OK" {
|
||||||
|
dm.Meta.LastOK = s.Time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dm.HistoricalData = tmp
|
||||||
|
|
||||||
|
dm.Meta.LastUpdate = time.Now()
|
||||||
|
dm.Meta.PercCrit = countStatus["Critical"] / countStatus["Total"] * 100
|
||||||
|
dm.Meta.PercWarn = countStatus["Warning"] / countStatus["Total"] * 100
|
||||||
|
dm.Meta.PercOK = countStatus["OK"] / countStatus["Total"] * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DashboardMetric) IsValid() (bool, string) {
|
||||||
|
if dm.Expires > 604800 || dm.Expires < 0 {
|
||||||
|
return false, "Expires not in range 0 < x < 640800"
|
||||||
|
}
|
||||||
|
|
||||||
|
if dm.Freshness > 604800 || dm.Freshness < 0 {
|
||||||
|
return false, "Freshness not in range 0 < x < 640800"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stringInSlice(dm.Status, []string{"OK", "Warning", "Critical", "Unknowm"}) {
|
||||||
|
return false, "Status not allowed"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dm.Title) > 512 || len(dm.Description) > 1024 {
|
||||||
|
return false, "Title or Description too long"
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, ""
|
||||||
|
}
|
103
templates/dashboard.html
Normal file
103
templates/dashboard.html
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>MonDash - Dashboard</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
|
||||||
|
|
||||||
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||||
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-default">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="/">MonDash</a>
|
||||||
|
</div><!-- /.navbar-header -->
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li><a href="/create">Get your own dashboard</a></li>
|
||||||
|
</ul><!-- /.navbar-right -->
|
||||||
|
</div><!-- /.navbar-collapse -->
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% if dashid == "welcome" %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="jumbotron text-center">
|
||||||
|
<h1>Welcome to MonDash!</h1>
|
||||||
|
<p>You're currently seeing a demo dashboard updated with random numbers below. To get started read the <a href="http://docs.mondash.apiary.io/" target="_blank">API documentation</a> and create your own dashboard by clicking the button in the upper right hand corner…
|
||||||
|
<p>If you have any questions about this project don't hesitate to ask <a href="https://luzifer.io/" target="_blank">Knut</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if metrics|length == 0 and dashid != "welcome" %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3 text-center">
|
||||||
|
<p>Welcome to your new dashboard. Your API-key is:</p>
|
||||||
|
<pre>{{ apikey }}</pre>
|
||||||
|
<p>After you sent your first metric you can reach your dashboard here:</p>
|
||||||
|
<a href="{{ baseurl }}/{{ dashid }}">{{ baseurl }}/{{ dashid }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% for metric in metrics %}
|
||||||
|
{% if metric.Status == "OK" %}
|
||||||
|
<div class="row alert alert-success">
|
||||||
|
{% elif metric.Status == "Warning" %}
|
||||||
|
<div class="row alert alert-warning">
|
||||||
|
{% elif metric.Status == "Critical" %}
|
||||||
|
<div class="row alert alert-danger">
|
||||||
|
{% else %}
|
||||||
|
<div class="row alert alert-info">
|
||||||
|
{% endif %}
|
||||||
|
<div class="col-md-9">
|
||||||
|
<h4>{{ metric.Title }}</h4>
|
||||||
|
<p>{{ metric.Description }}</p>
|
||||||
|
<small>
|
||||||
|
Updated {{ metric.Meta.LastUpdate|naturaltime}}
|
||||||
|
{% if metric.Status != "OK" %}
|
||||||
|
/ Last ok {{ metric.Meta.LastOK|naturaltime }}
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 hidden-xs">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-bar-success" style="width: {{ metric.Meta.PercOK }}%">
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar progress-bar-warning" style="width: {{ metric.Meta.PercWarn }}%">
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar progress-bar-danger" style="width: {{ metric.Meta.PercCrit }}%">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||||
|
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
welcome_runner.go
Normal file
52
welcome_runner.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunWelcomePage() {
|
||||||
|
baseURL := os.Getenv("BASE_URL")
|
||||||
|
welcomeAPIToken := os.Getenv("API_TOKEN")
|
||||||
|
generateTicker := time.NewTicker(time.Minute)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-generateTicker.C:
|
||||||
|
beers := rand.Intn(24)
|
||||||
|
status := "OK"
|
||||||
|
switch {
|
||||||
|
case beers < 6:
|
||||||
|
status = "Critical"
|
||||||
|
break
|
||||||
|
case beers < 12:
|
||||||
|
status = "Warning"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
beer := DashboardMetric{
|
||||||
|
Title: "Amount of beer in the fridge",
|
||||||
|
Description: fmt.Sprintf("Currently there are %d bottles of beer in the fridge", beers),
|
||||||
|
Status: status,
|
||||||
|
Expires: 86400,
|
||||||
|
Freshness: 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(beer)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf("%s/welcome/beer_available", baseURL)
|
||||||
|
req, _ := http.NewRequest("PUT", url, bytes.NewBuffer(body))
|
||||||
|
req.Header.Add("Authorization", welcomeAPIToken)
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue