2022-12-27 10:29:49 +01:00
|
|
|
import json
|
|
|
|
import logging
|
2022-12-29 13:40:47 +01:00
|
|
|
import time
|
2022-12-27 10:29:49 +01:00
|
|
|
|
|
|
|
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:
|
2022-12-29 13:40:47 +01:00
|
|
|
tso = LTS(id=ts)
|
|
|
|
if not tso.data:
|
|
|
|
log.warning("%s has no data: Skipping", tso.id)
|
|
|
|
continue
|
|
|
|
if tso.data[-1][0] < time.time() - 86400:
|
|
|
|
log.info("%s too old; Skipping", tso.id)
|
|
|
|
continue
|
2022-12-27 10:29:49 +01:00
|
|
|
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")
|
2022-12-27 10:54:19 +01:00
|
|
|
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}%)"
|
|
|
|
|
2022-12-27 10:29:49 +01:00
|
|
|
|
|
|
|
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])
|
2022-12-27 10:54:19 +01:00
|
|
|
max_value = max(max_value, 1) # ensure positive
|
2022-12-27 10:29:49 +01:00
|
|
|
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":
|
2022-12-27 11:10:06 +01:00
|
|
|
if value >= 365.25 * 86400:
|
|
|
|
value /= 365.25 * 86400
|
|
|
|
unit = "years"
|
|
|
|
elif value >= 86400:
|
2022-12-27 10:29:49 +01:00
|
|
|
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)
|