add flask app files
This commit is contained in:
parent
2f4d309280
commit
ecf6fa1457
|
@ -0,0 +1,22 @@
|
|||
#webapp
|
||||
Flask web app for data visalization. Client side is built with [Chart.js](https://chartjs.org) and [justgage](https://github.com/toorshia/justgage) js libraries and jQuery.
|
||||
|
||||
The app exposes a simple api for querying the database and source sensor status available at /api/*function*.
|
||||
The functions are:
|
||||
-/api/getData
|
||||
-Method: GET, POST
|
||||
-Number of samples can be specified with argument samples=*desired number*, defaults to 120 i.e 2 hours.
|
||||
-Returns json with the datasets and information about sensor status
|
||||
-/api/heartbeat
|
||||
-Method: GET
|
||||
-Returns json with status info and last received message timestamp
|
||||
-Method: POST
|
||||
-Reserved for internal use in reporting sensor status. Requires a secret to be set.
|
||||
|
||||
The client side comprises of several parts:
|
||||
-`templates/index.html` provides the basic html structure
|
||||
-`static/styles/chartstyle.css` takes care of some basic styling and layout
|
||||
-`static/main.js` script taking care of creating the plots and gauges, requesting data from server and updating the view every two minutes.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[uwsgi]
|
||||
module = wsgi
|
||||
|
||||
master = true
|
||||
processes = 2
|
||||
|
||||
socket = inenvmon_web.sock
|
||||
chmod-socket = 666
|
||||
vacuum = true
|
||||
|
||||
die-on-term = true
|
|
@ -0,0 +1,193 @@
|
|||
from flask import Flask, render_template, make_response, jsonify, request
|
||||
import mysql.connector as mariadb
|
||||
import datetime
|
||||
import time
|
||||
import io
|
||||
import json
|
||||
import pandas as pd
|
||||
from collections import OrderedDict
|
||||
|
||||
DB = ""
|
||||
DB_USER = ""
|
||||
DB_PASSWORD = ""
|
||||
API_SECRET = ""
|
||||
|
||||
class Source():
|
||||
def __init__(self):
|
||||
self.status = "unknown"
|
||||
self.last_msg = 0.0
|
||||
|
||||
source = Source()
|
||||
app = Flask(__name__)
|
||||
|
||||
def query_db(sql):
|
||||
dbconn = mariadb.connect(user=DB_USER, password=DB_PASSWORD, database=DB)
|
||||
cursor = dbconn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute(sql)
|
||||
except mariadb.Error as e:
|
||||
print(e)
|
||||
|
||||
data = cursor.fetchall()
|
||||
dbconn.close()
|
||||
|
||||
return data
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/api/getData', methods = ['GET','POST'])
|
||||
def process_data():
|
||||
if request.method == 'GET':
|
||||
if request.args.get('samples') is not None:
|
||||
try:
|
||||
samples = int(request.args.get('samples'))
|
||||
if samples > 10800:
|
||||
samples = 120
|
||||
except Exception as excp:
|
||||
print(excp)
|
||||
samples = 120
|
||||
else:
|
||||
samples = 120
|
||||
|
||||
elif request.method == 'POST':
|
||||
try:
|
||||
samples = int(request.form.get('samples'))
|
||||
except Exception as excp:
|
||||
print(excp)
|
||||
samples = 120
|
||||
|
||||
if samples > 10800:
|
||||
samples = 120
|
||||
|
||||
sql = "SELECT * FROM envdata ORDER BY timestamp DESC LIMIT %s" % samples
|
||||
dbdata = query_db(sql)
|
||||
|
||||
f = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
labels = []
|
||||
temps = []
|
||||
hums = []
|
||||
press = []
|
||||
concs = []
|
||||
|
||||
for i in range(len(dbdata)):
|
||||
labels.append(dbdata[i][0])
|
||||
temps.append(dbdata[i][1])
|
||||
hums.append(dbdata[i][2])
|
||||
press.append(dbdata[i][3])
|
||||
concs.append(dbdata[i][4])
|
||||
|
||||
df = pd.DataFrame({'dt': labels, 'temp': temps, 'hum': hums, 'pres': press, 'conc': concs})
|
||||
r = pd.date_range(start=df['dt'].min(), end=df['dt'].max(), freq='T')
|
||||
# df = df.drop_duplicates('dt').set_index('dt').reindex(r, method='pad', tolerance='90s').reset_index()
|
||||
df = df.set_index('dt').reindex(r, method='nearest', tolerance='90s').reset_index()
|
||||
|
||||
darry = df.to_numpy()
|
||||
start = len(darry)
|
||||
print("darry %s" % str(len(darry)))
|
||||
print("samples %s" % str(samples))
|
||||
print(start)
|
||||
print(start-samples)
|
||||
print(samples-start)
|
||||
|
||||
if len(darry) != samples:
|
||||
if samples > len(darry):
|
||||
labels=darry[samples-start:start:1,0]
|
||||
temps=darry[samples-start:start:1,1]
|
||||
hums=darry[samples-start:start:1,2]
|
||||
press=darry[samples-start:start:1,3]
|
||||
concs=darry[samples-start:start:1,4]
|
||||
else:
|
||||
labels=darry[start-samples:start:1,0]
|
||||
temps=darry[start-samples:start:1,1]
|
||||
hums=darry[start-samples:start:1,2]
|
||||
press=darry[start-samples:start:1,3]
|
||||
concs=darry[start-samples:start:1,4]
|
||||
elif len(darry) == 1:
|
||||
labels=darry[0,0]
|
||||
temps=darry[0,1]
|
||||
hums=darry[0,2]
|
||||
press=darry[0,3]
|
||||
concs=darry[0,4]
|
||||
else:
|
||||
labels=darry[0:start:1,0]
|
||||
temps=darry[0:start:1,1]
|
||||
hums=darry[0:start:1,2]
|
||||
press=darry[0:start:1,3]
|
||||
concs=darry[0:start:1,4]
|
||||
|
||||
tempdict = OrderedDict()
|
||||
humdict = OrderedDict()
|
||||
presdict = OrderedDict()
|
||||
co2dict = OrderedDict()
|
||||
|
||||
if len(darry) == 1:
|
||||
tempdict[labels.strftime(f)] = str(temps)
|
||||
humdict[labels.strftime(f)] = str(hums)
|
||||
presdict[labels.strftime(f)] = str(press)
|
||||
co2dict[labels.strftime(f)] = str(concs)
|
||||
else:
|
||||
for i in range(len(labels)):
|
||||
tempdict[labels[i].strftime(f)] = str(temps[i])
|
||||
humdict[labels[i].strftime(f)] = str(hums[i])
|
||||
presdict[labels[i].strftime(f)] = str(press[i])
|
||||
co2dict[labels[i].strftime(f)] = str(concs[i])
|
||||
|
||||
if (time.time() - source.last_msg) > 180:
|
||||
status = "dead"
|
||||
else:
|
||||
status = "alive"
|
||||
|
||||
respdict = OrderedDict()
|
||||
datas = ["status", "temp", "hum", "pres", "co2"]
|
||||
dicts = [status, tempdict, humdict, presdict, co2dict]
|
||||
|
||||
for i in range(len(datas)):
|
||||
respdict[datas[i]] = dicts[i]
|
||||
|
||||
response = app.response_class(
|
||||
response=json.dumps(respdict),
|
||||
status=200,
|
||||
mimetype='application/json'
|
||||
)
|
||||
return response
|
||||
|
||||
@app.route('/api/heartbeat', methods = ['GET','POST'])
|
||||
def heartbeat():
|
||||
global source
|
||||
if request.method == 'POST':
|
||||
key = request.form.get('key')
|
||||
if key == API_SECRET:
|
||||
if request.form.get('message') is not None:
|
||||
source.last_msg = float(request.form.get('message'))
|
||||
else:
|
||||
source.last_msg = "nodata"
|
||||
source.status = "unknown"
|
||||
|
||||
post_response = app.response_class(
|
||||
status=200)
|
||||
return post_response
|
||||
else:
|
||||
err_response = app.response_class(
|
||||
status=401)
|
||||
return err_response
|
||||
|
||||
if request.method == 'GET':
|
||||
if (time.time() - source.last_msg) > 180:
|
||||
source.status = "dead"
|
||||
else:
|
||||
source.status = "alive"
|
||||
|
||||
resp_data = {'status': source.status, 'last_msg': source.last_msg}
|
||||
get_response=app.response_class(
|
||||
response=json.dumps(resp_data),
|
||||
status=200,
|
||||
mimetype='application/json'
|
||||
)
|
||||
return get_response
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,443 @@
|
|||
var temp_gauge = new JustGage({
|
||||
id: 'temp_gauge',
|
||||
value: 0,
|
||||
min: 10,
|
||||
max: 50,
|
||||
decimals: 2,
|
||||
width: 222,
|
||||
height: 150,
|
||||
symbol: ' °C',
|
||||
showInnerShadow: true,
|
||||
levelColors: ['#3300ff', '#fff200', '#ff0008'],
|
||||
customSectors: {
|
||||
percents: false,
|
||||
ranges: [{
|
||||
color : "#33ff00",
|
||||
lo : 20,
|
||||
hi : 25.5
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
var hum_gauge = new JustGage({
|
||||
id: "hum_gauge",
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 100,
|
||||
decimals: 2,
|
||||
width: 222,
|
||||
height: 150,
|
||||
symbol: ' %',
|
||||
showInnerShadow: true,
|
||||
customSectors: {
|
||||
percents: false,
|
||||
ranges: [{
|
||||
color : "#33ff00",
|
||||
lo : 30,
|
||||
hi : 60
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
var pres_gauge = new JustGage({
|
||||
id: "pres_gauge",
|
||||
value: 0,
|
||||
min: 900,
|
||||
max: 1050,
|
||||
decimals: 0,
|
||||
width: 222,
|
||||
height: 150,
|
||||
symbol: ' hPa',
|
||||
showInnerShadow: true,
|
||||
noGradient: true,
|
||||
customSectors: {
|
||||
percents: true,
|
||||
ranges: [{
|
||||
color : "#2200ff",
|
||||
lo : 0,
|
||||
hi : 100
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
var co2_gauge = new JustGage({
|
||||
id: "co2_gauge",
|
||||
value: 0,
|
||||
min: 300,
|
||||
max: 5000,
|
||||
decimals: 0,
|
||||
width: 222,
|
||||
height: 150,
|
||||
symbol: ' ppm',
|
||||
showInnerShadow: true,
|
||||
levelColors: ['#33ff00', '#ffff00', '#ff8800', '#ff0008']
|
||||
});
|
||||
|
||||
function updateGauge(dbdata) {
|
||||
for(var key in dbdata.temp){};
|
||||
temp_gauge.refresh(dbdata.temp[key]);
|
||||
hum_gauge.refresh(dbdata.hum[key]);
|
||||
pres_gauge.refresh(dbdata.pres[key]);
|
||||
co2_gauge.refresh(dbdata.co2[key]);
|
||||
};
|
||||
|
||||
var reqform = $('#sample_select_form');
|
||||
reqform.submit(function (f) {
|
||||
f.preventDefault();
|
||||
clearChartData();
|
||||
getServerDataAndUpdate($('input[name=samples]').val());
|
||||
});
|
||||
|
||||
setInterval(function fx() {
|
||||
var statusData = $.ajax ({
|
||||
type: 'GET',
|
||||
url: '/api/heartbeat',
|
||||
data: {},
|
||||
dataType: 'json',
|
||||
success: console.log("requested heartbeat data"),
|
||||
}).done(function (retdata) {
|
||||
if ( typeof fx.statusWas === 'undefined' ) {
|
||||
fx.statusWas = "dead";
|
||||
};
|
||||
|
||||
if (retdata.status == "alive" && fx.statusWas == "alive") {
|
||||
getServerDataAndUpdate(1);
|
||||
trimChartData();
|
||||
fx.statusWas = "alive";
|
||||
}
|
||||
else if (retdata.status == "alive" && fx.statusWas == "dead") {
|
||||
$('#footer_text').text("Live update enabled. (Data might be delayed up to a minute)");
|
||||
clearChartData();
|
||||
getServerDataAndUpdate($('input[name=samples]').val());
|
||||
fx.statusWas = "alive";
|
||||
}
|
||||
else if (retdata.status == "dead" && fx.statusWas == "alive") {
|
||||
$('#footer_text').text("Source not online, live update disabled. Showing latest available data.");
|
||||
fx.statusWas = "dead";
|
||||
}
|
||||
else if (retdata.status == "dead" && fx.statusWas == "dead") {
|
||||
fx.statusWas == "dead";
|
||||
}
|
||||
else {
|
||||
location.reload(forceGet);
|
||||
}
|
||||
});
|
||||
}, 90000);
|
||||
|
||||
$('#refresh').click(function() {
|
||||
clearChartData();
|
||||
getServerDataAndUpdate($('input[name=samples]').val());
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
var statusData = $.ajax ({
|
||||
type: 'GET',
|
||||
url: '/api/heartbeat',
|
||||
data: {},
|
||||
dataType: 'json',
|
||||
success: console.log("requested heartbeat data"),
|
||||
}).done(function (retdata) {
|
||||
if (retdata.status == "alive") {
|
||||
$('#footer_text').text("Live update enabled. (Data might be delayed up to a minute)");
|
||||
}
|
||||
else if (retdata.status == "dead") {
|
||||
$('#footer_text').text("Source not online, live update disabled. Showing latest available data.");
|
||||
}
|
||||
});
|
||||
getServerDataAndUpdate($('input[name=samples]').val());
|
||||
});
|
||||
|
||||
Chart.defaults.global.responsive = false;
|
||||
var nan=NaN;
|
||||
|
||||
var tempChartData = {
|
||||
labels : [],
|
||||
datasets : [{
|
||||
label: 'Temperature',
|
||||
fill: true,
|
||||
lineTension: 0.1,
|
||||
backgroundColor: "rgba(75,192,192,0.4)",
|
||||
borderColor: "rgba(75,192,192,1)",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(75,192,192,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(75,192,192,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
showLine: true,
|
||||
spanGaps: false,
|
||||
data : []
|
||||
}]
|
||||
};
|
||||
|
||||
var tempChartSettings = {
|
||||
type : 'line',
|
||||
data : tempChartData,
|
||||
options : {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales : {
|
||||
xAxes : [{
|
||||
distribution: 'series',
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 20,
|
||||
},
|
||||
type: 'time',
|
||||
source: 'auto'
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var humChartData = {
|
||||
labels : [],
|
||||
datasets : [{
|
||||
label: 'Humidity',
|
||||
fill: true,
|
||||
lineTension: 0.1,
|
||||
backgroundColor: "rgba(136,77,255,0.4)",
|
||||
borderColor: "rgba(136,77,255,1)",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(136,77,255,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(136,77,255,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
data : [],
|
||||
spanGaps: false,
|
||||
}]
|
||||
};
|
||||
|
||||
var humChartSettings = {
|
||||
type : 'line',
|
||||
showLines: false,
|
||||
data : humChartData,
|
||||
options : {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales : {
|
||||
xAxes : [{
|
||||
distribution: 'series',
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 20,
|
||||
},
|
||||
type: 'time',
|
||||
source: 'auto'
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var co2ChartData = {
|
||||
labels : [],
|
||||
datasets : [{
|
||||
label: 'CO2 concentration',
|
||||
fill: true,
|
||||
lineTension: 0.1,
|
||||
backgroundColor: "rgba(0,128,43,0.4)",
|
||||
borderColor: "rgba(0,128,43,1)",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(0,128,43,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(0,128,43,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
data : [],
|
||||
spanGaps: false,
|
||||
}]
|
||||
};
|
||||
|
||||
var co2ChartSettings = {
|
||||
type : 'line',
|
||||
showLines: false,
|
||||
data : co2ChartData,
|
||||
options : {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales : {
|
||||
xAxes : [{
|
||||
distribution: 'series',
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 20,
|
||||
},
|
||||
type: 'time',
|
||||
source: 'auto'
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
min: 300,
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var multiChartData = {
|
||||
labels : [],
|
||||
datasets : [{
|
||||
label: 'Temperature',
|
||||
fill: true,
|
||||
lineTension: 0.1,
|
||||
backgroundColor: "rgba(75,192,192,0.4)",
|
||||
borderColor: "rgba(75,192,192,1)",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(75,192,192,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(75,192,192,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
data : [],
|
||||
spanGaps: false,
|
||||
}, {
|
||||
label: 'Humidity',
|
||||
fill: true,
|
||||
lineTension: 0.1,
|
||||
backgroundColor: "rgba(136,77,255,0.4)",
|
||||
borderColor: "rgba(136,77,255,1)",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(136,77,255,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(136,77,255,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
data : [],
|
||||
spanGaps: false,
|
||||
}]
|
||||
};
|
||||
|
||||
var multiChartSettings = {
|
||||
type : 'line',
|
||||
showLines: false,
|
||||
data : multiChartData,
|
||||
options : {
|
||||
scales : {
|
||||
xAxes : [{
|
||||
distribution: 'series',
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 20,
|
||||
},
|
||||
type: 'time',
|
||||
source: 'auto'
|
||||
}],
|
||||
yAxes : [{
|
||||
ticks: {
|
||||
min: 10
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var ctx = document.getElementById('tempchart').getContext("2d");
|
||||
var tempChart = new Chart(ctx,tempChartSettings);
|
||||
|
||||
var ctx2 = document.getElementById('humchart').getContext("2d");
|
||||
var humChart = new Chart(ctx2,humChartSettings);
|
||||
|
||||
var ctx3 = document.getElementById('co2chart').getContext("2d");
|
||||
var co2Chart = new Chart(ctx3,co2ChartSettings);
|
||||
|
||||
var ctx4 = document.getElementById('multichart').getContext("2d");
|
||||
var multiChart = new Chart(ctx4,multiChartSettings);
|
||||
|
||||
function getServerDataAndUpdate(samples) {
|
||||
var jsonData = $.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/getData',
|
||||
data: {'samples' : samples },
|
||||
dataType: 'json',
|
||||
success: console.log("requested db data"),
|
||||
}).done(function (dbdata) {
|
||||
updateChart(dbdata);
|
||||
updateGauge(dbdata);
|
||||
});
|
||||
};
|
||||
|
||||
function updateChart(dbdata) {
|
||||
$.each(dbdata['temp'], function(key, value) {
|
||||
tempChart.data.labels.push(key);
|
||||
humChart.data.labels.push(key);
|
||||
co2Chart.data.labels.push(key);
|
||||
multiChart.data.labels.push(key);
|
||||
tempChart.data.datasets[0].data.push(value);
|
||||
multiChart.data.datasets[0].data.push(value);
|
||||
});
|
||||
$.each(dbdata['hum'], function(key, value){
|
||||
humChart.data.datasets[0].data.push(value);
|
||||
multiChart.data.datasets[1].data.push(value);
|
||||
});
|
||||
$.each(dbdata['co2'], function(key, value){
|
||||
co2Chart.data.datasets[0].data.push(value);
|
||||
});
|
||||
|
||||
tempChart.update();
|
||||
humChart.update();
|
||||
co2Chart.update();
|
||||
multiChart.update();
|
||||
};
|
||||
|
||||
function clearChartData() {
|
||||
tempChart.data.datasets[0].data = [];
|
||||
tempChart.data.labels = [];
|
||||
humChart.data.datasets[0].data = [];
|
||||
humChart.data.labels = [];
|
||||
co2Chart.data.datasets[0].data = [];
|
||||
co2Chart.data.labels = [];
|
||||
multiChart.data.datasets[0].data = [];
|
||||
multiChart.data.datasets[1].data = [];
|
||||
multiChart.data.labels = [];
|
||||
};
|
||||
|
||||
function trimChartData() {
|
||||
tempChart.data.datasets[0].data.shift();
|
||||
tempChart.data.labels.shift();
|
||||
humChart.data.datasets[0].data.shift();
|
||||
humChart.data.labels.shift();
|
||||
co2Chart.data.datasets[0].data.shift();
|
||||
co2Chart.data.labels.shift();
|
||||
multiChart.data.datasets[0].data.shift();
|
||||
multiChart.data.datasets[1].data.shift();
|
||||
multiChart.data.labels.shift();
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}
|
|
@ -0,0 +1,76 @@
|
|||
#header_bar {
|
||||
}
|
||||
|
||||
#footer_bar {
|
||||
padding-bottom: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.gauge_container {
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.gauge {
|
||||
display: inline-block;
|
||||
width: 240px;
|
||||
height: 160px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.gauge_desc {
|
||||
margin: 0;
|
||||
margin-top: -30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.graph_name {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#sample_select {
|
||||
margin-top: 40px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#uberschrift {
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
margin-left: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#content_wrapper {
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#content_left {
|
||||
flex: 0 0 20%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#content_center {
|
||||
flex: 0 0 33%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#content_right {
|
||||
flex: 0 0 33%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: 'Noto Sans', Noto, sans-serif;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>IAQ dash</title>
|
||||
<link rel='shortcut icon' type='image/ico' href='static/favicon.ico'>
|
||||
<link rel='stylesheet' href='static/styles/chartstyle.css'>
|
||||
<link rel='stylesheet' href='static/styles/Chart.min.css'>
|
||||
<script type='text/javascript' src='static/Chart.bundle.min.js'></script>
|
||||
<script type='text/javascript' src='static/jquery.min.js'></script>
|
||||
<script type='text/javascript' src='static/raphael.min.js'></script>
|
||||
<script type='text/javascript' src='static/justgage.min.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='header_bar'>
|
||||
<h1 id='uberschrift'>Indoor enviro data</h1>
|
||||
</div>
|
||||
<div id='content_wrapper' class='column'>
|
||||
<div id='content_left' class='column'>
|
||||
<div class='gauge_container'>
|
||||
<h3>Most recent values</h3>
|
||||
<div id='temp_gauge' class='gauge' align='center'></div>
|
||||
<h5 align='center' class='gauge_desc'>Temperature</h5>
|
||||
<div id='hum_gauge' class='gauge' align='center'></div>
|
||||
<h5 align='center' class='gauge_desc'>Relative humidity</h5>
|
||||
<div id='pres_gauge' class='gauge' align='center'></div>
|
||||
<h5 align='center' class='gauge_desc'>Pressure</h5>
|
||||
<div id='co2_gauge' class='gauge' align='center'></div>
|
||||
<h5 align='center' class='gauge_desc'>CO2 concentration</h5>
|
||||
</div>
|
||||
<div id='sample_select'>
|
||||
<form id='sample_select_form' action='/'>
|
||||
<p>Show last <input type='number' min=1 max=10800 name='samples' placeholder='120' size=8> minutes.
|
||||
<input type='submit' value='Select'/></p>
|
||||
</form>
|
||||
<p><input type='button' id='refresh' value='Refresh all'/></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id='content_center' class='column'>
|
||||
<h3 class='graph_name'>Temperature</h3>
|
||||
<canvas id='tempchart' width='600' height='380' style='display: block;'></canvas>
|
||||
<h3 class='graph_name'>Humidity</h3>
|
||||
<canvas id='humchart' width='600' height='380' style='display: block;'></canvas>
|
||||
</div>
|
||||
<div id='content_right' class='column'>
|
||||
<h3 class='graph_name'>Combined</h3>
|
||||
<canvas id='multichart' width='600' height='380' style='display: block'></canvas>
|
||||
<h3 class='graph_name'>CO2 concentration</h3>
|
||||
<canvas id='co2chart' width='600' height='380' style='display: block;'></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div id='footer_bar'>
|
||||
<p id='footer_text'>Source not online, live update disabled. Showing latest available data.</p>
|
||||
</div>
|
||||
|
||||
<script type='text/javascript' src='static/main.js'></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
from inenvmon_web import app as application
|
||||
|
||||
if __name__ == "__main__":
|
||||
application.run()
|
Loading…
Reference in New Issue