Add dashboard with gauges
This commit is contained in:
parent
2f400fccee
commit
42ba485794
11
app.py
11
app.py
|
@ -8,6 +8,7 @@ import os
|
|||
from flask import (Flask, request, jsonify, abort, render_template)
|
||||
|
||||
from ltsdb_json import LTS
|
||||
from dashboard import Dashboard
|
||||
|
||||
import config
|
||||
|
||||
|
@ -125,3 +126,13 @@ def visualize():
|
|||
ts = LTS(id=id)
|
||||
timeseries_data.append(ts)
|
||||
return render_template("visualize.html", ts=timeseries_data)
|
||||
|
||||
@app.get("/dashboard/")
|
||||
def dashboard_index():
|
||||
d = Dashboard("dashboards/" + "index" + ".json")
|
||||
return d.as_html()
|
||||
|
||||
@app.get("/dashboard/<dashboard>")
|
||||
def dashboard_file(dashboard):
|
||||
d = Dashboard("dashboards/" + dashboard + ".json")
|
||||
return d.as_html()
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from flask import (Flask, request, jsonify, abort, render_template_string, render_template)
|
||||
from markupsafe import Markup
|
||||
|
||||
from ltsdb_json import LTS
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Dashboard:
|
||||
def __init__(self, filename="dashboard.json"):
|
||||
with open (filename) as fh:
|
||||
d = json.load(fh)
|
||||
self.title = d["title"]
|
||||
log.debug("title = “%s”", self.title)
|
||||
self.description = d["description"]
|
||||
self.widgets = []
|
||||
for w in d["widgets"]:
|
||||
if w["type"] == "timeseries":
|
||||
self.widgets.append(TimeSeries(w))
|
||||
elif w["type"] == "gauge":
|
||||
if w.get("multi"):
|
||||
ts_list = LTS.find(w["data"][0])
|
||||
for ts in ts_list:
|
||||
w1 = {**w, "data": [ts]}
|
||||
self.widgets.append(Gauge(w1))
|
||||
else:
|
||||
self.widgets.append(Gauge(w))
|
||||
else:
|
||||
self.widgets.append(Widget(w))
|
||||
|
||||
def as_html(self):
|
||||
return render_template("dashboard.html", dashboard=self)
|
||||
|
||||
|
||||
|
||||
class Widget:
|
||||
def __init__(self, d):
|
||||
self.type = d["type"]
|
||||
self.stops = d["stops"]
|
||||
log.debug("data = %s", d["data"])
|
||||
self.lts = LTS(id=d["data"][0]) # by default we handle only one data source
|
||||
pass
|
||||
|
||||
def as_html(self):
|
||||
log.debug("")
|
||||
self.lastvalue = self.lts.data[-1][1]
|
||||
return Markup(render_template("widget.html", widget=self))
|
||||
|
||||
@property
|
||||
def criticalcolor(self):
|
||||
log.debug("stops = %s", self.stops)
|
||||
brightness = 30
|
||||
if self.stops[0] < self.stops[2]:
|
||||
if self.lastvalue < self.stops[0]:
|
||||
log.debug("definitely ok")
|
||||
return f"hsl(120, 100%, {brightness}%)"
|
||||
elif self.lastvalue < self.stops[1]:
|
||||
log.debug("mostly ok")
|
||||
hue = 120 - round(
|
||||
(self.lastvalue - self.stops[0])
|
||||
/ (self.stops[1] - self.stops[0])
|
||||
* 60
|
||||
)
|
||||
return f"hsl({hue}, 100%, {brightness}%)"
|
||||
elif self.lastvalue < self.stops[2]:
|
||||
log.debug("maybe fail")
|
||||
hue = 60 - round(
|
||||
(self.lastvalue - self.stops[1])
|
||||
/ (self.stops[2] - self.stops[1])
|
||||
* 60
|
||||
)
|
||||
return f"hsl({hue}, 100%, {brightness}%)"
|
||||
else:
|
||||
log.debug("definitely fail")
|
||||
return f"hsl(0, 100%, {brightness}%)"
|
||||
else:
|
||||
log.debug("the other side")
|
||||
return "#CCC"
|
||||
|
||||
class TimeSeries(Widget):
|
||||
def __init__(self, d):
|
||||
super().__init__(d)
|
||||
pass
|
||||
|
||||
class Gauge(Widget):
|
||||
def __init__(self, d):
|
||||
super().__init__(d)
|
||||
pass
|
||||
|
||||
gaugesize = 100
|
||||
|
||||
@property
|
||||
def gaugepos(self):
|
||||
max_value = max([d[1] for d in self.lts.data])
|
||||
log.debug("max_value = %s", max_value)
|
||||
return self.lastvalue / max_value * self.gaugesize
|
||||
|
||||
def as_html(self):
|
||||
log.debug("")
|
||||
self.lastvalue = self.lts.data[-1][1]
|
||||
value = self.lastvalue
|
||||
unit = self.lts.description["unit"]
|
||||
log.debug("unit = %s", unit)
|
||||
if unit == "s":
|
||||
if value >= 86400:
|
||||
value /= 86400
|
||||
unit = "days"
|
||||
elif value >= 3600:
|
||||
value /= 3600
|
||||
unit = "h"
|
||||
elif value >= 60:
|
||||
value /= 60
|
||||
unit = "m"
|
||||
elif value >= 1:
|
||||
pass
|
||||
elif value >= 0.001:
|
||||
value *= 1000
|
||||
unit = "ms"
|
||||
self.lastvalue_formatted = Markup(f"<span class='value'>{value:.2f}</span><span class='unit'>{unit}</unit>")
|
||||
return Markup(render_template("gauge.html", widget=self))
|
||||
|
||||
@property
|
||||
def description_formatted(self):
|
||||
s = "<table class='description'>"
|
||||
for d, v in self.lts.description.items():
|
||||
if v:
|
||||
s += render_template_string(
|
||||
"<tr><th>{{d}}:</th><td>{{v}}</td></tr>",
|
||||
d=d, v=v)
|
||||
s += "</table>"
|
||||
return Markup(s)
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
}
|
||||
.widget {
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
.value {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.unit {
|
||||
display: block;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.gauge {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: end;
|
||||
border: 1px solid #CCC;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.gauge-container {
|
||||
width: 20px;
|
||||
border-radius: 4px;
|
||||
background-color: #CCC;
|
||||
position: relative;
|
||||
}
|
||||
.gauge-indicator {
|
||||
width: 20px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ dashboard.title }}</h1>
|
||||
<p class="description">
|
||||
{{ dashboard.description }}
|
||||
</p>
|
||||
<main>
|
||||
{% for widget in dashboard.widgets %}
|
||||
{{ widget.as_html() }}
|
||||
{% endfor %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<div class="gauge">
|
||||
<div class="gauge-container"
|
||||
style="height: {{widget.gaugesize}}px">
|
||||
<div class="gauge-indicator"
|
||||
style="height: {{widget.gaugepos}}px;
|
||||
background-color: {{widget.criticalcolor}}">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: {{widget.criticalcolor}}">
|
||||
{{ widget.lastvalue_formatted}}
|
||||
</span>
|
||||
</div>
|
||||
{{ widget.description_formatted }}
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
<div class="widget">
|
||||
<p>Unknown widget type {{ widget.type }}.</p>
|
||||
<p>
|
||||
Last value:
|
||||
<span class="value" style="color: {{widget.criticalcolor}}">{{ widget.lastvalue}}</span>
|
||||
</p>
|
||||
|
||||
</div>
|
Loading…
Reference in New Issue