9 Commits
master ... tmp

Author SHA1 Message Date
19fcc4d7de fix setting cursor when clicked
cursor should not be set when dragging (pan)
2024-09-25 10:48:28 +02:00
ce5d489b2d show time labels only on first chart 2024-09-25 09:23:04 +02:00
5f5e336370 improve time tick labels more 2024-09-25 09:02:34 +02:00
ac542b1694 improve time axis labels
+ change poll interval to 1 sec
2024-09-25 07:51:43 +02:00
4e27d66d36 fix mechanism to strip overlapping data for live update
fix bug when when there was no last value yet
2024-09-25 07:46:28 +02:00
638f77a047 Doc + working live 2024-09-13 16:40:06 +02:00
45e1957fd7 Autoscale considers new ChartJS format, data reloads onzoom and onpan 2024-09-13 16:11:40 +02:00
d5a5c6553e Zoom and pan are synchronized, changed afterBuildTicksSignature 2024-09-13 15:47:53 +02:00
ab9f7b8ab0 initial test + libraries 2024-09-13 11:45:06 +02:00
11 changed files with 265 additions and 171 deletions

View File

@ -4,6 +4,27 @@ The WEB GUI client of SEA.
This repository contains the code of the server for the control and graphical parts, plus the client code (HTML, CSS, JS).
**IMPORTANT**
This branch is an attempt to migrate from ChartJS 2.9.4 to 4.4.4.
TESTED ON SAFARI : with this new version, the application takes much less RAM, and does not crash at some point. The user can still experience some latencies, but it might be due to the presence of too many time axis labels + the fact that each graphs has its own (for the moment).
Here is a list of what has been done :
- Uprgaded the ChartJS zoom plugin library, and changed the corresponding options in the chart configuration. The previous version was not working with the version 4.4.4 of ChartJS
- Installing the date library Luxon and its adpater for ChartJS. This is needed because since version 3.x, time axes need these libraries.
- Renamed or moved all needed parameters in the ChartJS configuration.
- Changed all `xAxes` and `yAxes` references to `x` and `y`.
- Adapted `afterBuildTicks` callbacks with the new signature (only `axis` is given)
- Changed all references to `ticks.max|min` : these two properties are one step higher, at the level of the axis, not the ticks
Here is a list of what needs to be done :
- Change the implementation of the callback located in `options.scales.x.ticks` at chart creation in the Chart class, so it considers that the label is a timestamp. Reference : https://www.chartjs.org/docs/latest/axes/labelling.html#creating-custom-tick-formats
- Labels of the x axis are not displayed in the desired format, and do not rescale properly on zooming/dezooming. There can be too much labels, that make them rotate and invisible.
- The cursor now also displays when the click ends, which is not the same behavior as before.
- Make the zoom type toggle work again.
- Make the zoom via touchpad less sensitive. The recent tests have shown that the zoom via gesture is very sensitive. Two things can be looked for : 1. see if there is the possibility to adapt the sensitivity of the zoom for the touchpad only or 2. update the library Hammer.js which is used by ChartJS to handle this type of gesture (even if the current version is 0.0.1 version later than the last one, this might be an explanation).
- Display only one time axis.
**Summary**
- [Documentation](#documentation)

View File

@ -41,9 +41,14 @@
<script src="externalFiles/eventsource.js"></script>
<!-- <script src="externalFiles/d3.min.js"></script> -->
<script src="externalFiles/swiper-bundle.min.js"></script>
<script src="externalFiles/Chart.bundle.min.js"></script>
<!-- <script src="externalFiles/Chart.bundle.min.js"></script> -->
<script src="externalFiles/chart.umd.min.js"></script>
<script src="externalFiles/luxon.min.js"></script>
<script src="externalFiles/chartjs-adapter-luxon.umd.min.js"></script>
<script src="externalFiles/hammer.js"></script>
<script src="externalFiles/chartjs-zoom.js"></script>
<script src="externalFiles/chartjs-plugin-zoom.min.js"></script>
<!-- <script src="externalFiles/chartjs-zoom.js"></script> -->
<script src="jsFiles/SEAWebClientLocalStorage.js"></script>
<script src="jsFiles/SEAWebClientResponsivity.js"></script>
<script src="jsFiles/SEAWebClientSwiper.js"></script>

File diff suppressed because one or more lines are too long

20
client/externalFiles/chart.umd.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
/*!
* chartjs-adapter-luxon v1.3.1
* https://www.chartjs.org
* (c) 2023 chartjs-adapter-luxon Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("chart.js"),require("luxon")):"function"==typeof define&&define.amd?define(["chart.js","luxon"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Chart,e.luxon)}(this,(function(e,t){"use strict";const n={datetime:t.DateTime.DATETIME_MED_WITH_SECONDS,millisecond:"h:mm:ss.SSS a",second:t.DateTime.TIME_WITH_SECONDS,minute:t.DateTime.TIME_SIMPLE,hour:{hour:"numeric"},day:{day:"numeric",month:"short"},week:"DD",month:{month:"short",year:"numeric"},quarter:"'Q'q - yyyy",year:{year:"numeric"}};e._adapters._date.override({_id:"luxon",_create:function(e){return t.DateTime.fromMillis(e,this.options)},init(e){this.options.locale||(this.options.locale=e.locale)},formats:function(){return n},parse:function(e,n){const i=this.options,r=typeof e;return null===e||"undefined"===r?null:("number"===r?e=this._create(e):"string"===r?e="string"==typeof n?t.DateTime.fromFormat(e,n,i):t.DateTime.fromISO(e,i):e instanceof Date?e=t.DateTime.fromJSDate(e,i):"object"!==r||e instanceof t.DateTime||(e=t.DateTime.fromObject(e,i)),e.isValid?e.valueOf():null)},format:function(e,t){const n=this._create(e);return"string"==typeof t?n.toFormat(t):n.toLocaleString(t)},add:function(e,t,n){const i={};return i[n]=t,this._create(e).plus(i).valueOf()},diff:function(e,t,n){return this._create(e).diff(this._create(t)).as(n).valueOf()},startOf:function(e,t,n){if("isoWeek"===t){n=Math.trunc(Math.min(Math.max(0,n),6));const t=this._create(e);return t.minus({days:(t.weekday-n+7)%7}).startOf("day").valueOf()}return t?this._create(e).startOf(t).valueOf():e},endOf:function(e,t){return this._create(e).endOf(t).valueOf()}})}));

File diff suppressed because one or more lines are too long

1
client/externalFiles/luxon.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -395,9 +395,11 @@ function loadCurvesSettingsPopup(){
let graphs = (function (){
let dataset_to_graph_map = {}; // a dictionnary mapping a variable name to a two values array, containing its graph index and its position inside the graph
let blocks, liveMode=true, top_vars=[], bottom_vars=[];
let legendFlag = false, currentZoomMode = isTouchDevice ? 'xy' : 'x';
let legendFlag = false;
let currentZoomMode = isTouchDevice ? 'xy' : 'x';
let prevTime = null, prevMin = null, prevMax = null, prevGraph = null; // zoom speed limitation
let cursorLinePos = null; // the position of the cursor line (given by its x value)
let clickMode = 0; // 1: mouse is down, 2: pan is active, 0: after mouse down
let type = 'linear'; // type of graphs axis to display
@ -601,8 +603,8 @@ let graphs = (function (){
* @returns If the minimun y-value of all the curves of the charts is greater than the maximum y-value (same)
*/
function autoScale(chart) {
axis = chart.options.scales.yAxes[0];
tax = chart.options.scales.xAxes[0].ticks;
ay = chart.options.scales.y;
ax = chart.options.scales.x;
datasets = chart.data.datasets;
let max = -1e99;
let min = 1e99;
@ -613,8 +615,8 @@ let graphs = (function (){
for (let i = 0; i < datasets.length; i++){
ds = datasets[i];
if (ds.borderWidth == 1) continue;
let lmax = maxAr(ds.data, tax.min, tax.max);
let lmin = minAr(ds.data, tax.min, tax.max);
let lmax = maxAr(ds.data, ax.min, ax.max);
let lmin = minAr(ds.data, ax.min, ax.max);
if(lmax > max)
max = lmax;
if(lmin < min)
@ -648,8 +650,8 @@ let graphs = (function (){
}
extraMin = Math.min(min - ystep * 0.5, extraMin);
extraMax = Math.max(max + ystep * 0.5, extraMax);
if (min >= axis.ticks.min && axis.ticks.min >= extraMin &&
max <= axis.ticks.max && axis.ticks.max <= extraMax) {
if (min >= ay.min && ay.min >= extraMin &&
max <= ay.max && ay.max <= extraMax) {
//console.log('NOCHANGE', max, axis.ticks.max, extraMax)
return; // do not yet change
}
@ -658,8 +660,8 @@ let graphs = (function (){
min = extraMin;
max = extraMax;
}
axis.min = axis.ticks.min = min;
axis.max = axis.ticks.max = max;
ay.min = min;
ay.max = max;
}
/**
@ -701,9 +703,13 @@ let graphs = (function (){
legendFlag = true;
let trect = evt.target.getBoundingClientRect();
let X = evt.clientX - trect.x, Y = evt.clientY - trect.y;
menuGraphicsPopup.hide();
showLegends(true, false);
cursorLine(X);
if (X == cursorLinePos) {
cursorLine(null);
} else {
menuGraphicsPopup.hide();
showLegends(true, false);
cursorLine(X);
}
setLiveMode();
update();
for (let gr of graph_array.slice(0, ngraphs)) {
@ -714,7 +720,18 @@ let graphs = (function (){
}
}
}
container.addEventListener('click', clickHandler)
function mouseDown(evt) {
clickMode = 1;
}
function mouseUp(evt) {
if (clickMode == 1) { // mouse was down, but no pan happend
clickHandler(evt);
}
clickMode = 0;
}
// container.addEventListener('click', clickHandler)
container.addEventListener('mousedown', mouseDown);
container.addEventListener('mouseup', mouseUp);
/**
* Sets (overwrite) the data (curve) of the given variable
@ -788,8 +805,8 @@ let graphs = (function (){
* @returns When data is received (no need to autoScale and update as it is done in reloadData)
*/
function checkReload(graph){
let tk = graph.chart.options.scales.xAxes[0].ticks;
let xmin = tk.min, xmax = tk.max;
let ax = graph.chart.options.scales.x;
let xmin = ax.min, xmax = ax.max;
/*
if (xmax < now()-100000) { // was 100000 = 100sec
if (liveMode) console.log('UPDATES OFF?')
@ -828,14 +845,14 @@ let graphs = (function (){
* @param {*} graph - The graph Object on which the zoom callback has to be called
*/
function zoomCallback(graph){
let tk, min, max;
let a, min, max;
if (currentZoomMode == 'y') {
tk = graph.chart.options.scales.yAxes[0].ticks;
a = graph.chart.options.scales.y;
} else {
tk = graph.chart.options.scales.xAxes[0].ticks;
a = graph.chart.options.scales.x;
}
min = tk.min;
max = tk.max;
min = a.min;
max = a.max;
if (!isTouchDevice) {
/*
if (prevGraph != graph) {
@ -859,8 +876,8 @@ let graphs = (function (){
*/
}
if (currentZoomMode == 'y') {
tk.min = min;
tk.max = max;
a.min = min;
a.max = max;
graph.setAutoScale(false);
} else {
if (liveMode && max < lastTime) setLiveMode(false);
@ -924,8 +941,9 @@ let graphs = (function (){
* @param {*} graph - The graph for which the function has to be called
*/
function panCallback(graph){
let tk = graph.chart.options.scales.xAxes[0].ticks;
let xmin = tk.min, xmax = tk.max;
let ax = graph.chart.options.scales.x;
let xmin = ax.min, xmax = ax.max;
clickMode = 2; // mouse pan mode
if (liveMode && xmax < lastTime) setLiveMode(false);
setMinMax(xmin,xmax);
update();
@ -1363,119 +1381,138 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
parent.appendChild(canvas);
let ctx = canvas.getContext("2d");
let self = this;
chart = new Chart(ctx, {
type: 'scatter',
options: {
responsive: true,
maintainAspectRatio: false,
animation:{duration:0},
scales: {
yAxes: [{ticks: {
beginAtZero: false,
mirror: true,
padding: -10,
//workaround for proper number format
callback: function(label, index, labels) {
if(index == 0 || index == labels.length-1)
return "";
return strFormat(label);
}
},
gridLines:{drawTicks:false},
scaleLabel: false, // {display: true, labelString: y_label},
type: scaleType,
position: 'right',
afterBuildTicks: function(axis, ticks) {
if (scaleType == "logarithmic" && ticks.length <= 4) {
y1 = ticks[0];
y0 = ticks.slice(-1)[0];
span = y1 - y0;
step = Math.abs(span * 0.3).toExponential(0);
if (step[0] > '5') {
step = '5' + step.substr(1);
} else if (step[0] > '2') {
step = '2' + step.substr(1);
}
step = Number.parseFloat(step);
ticks = [y1];
for (let yt = Math.ceil(y1 / step) * step; yt > y0; yt -= step) {
ticks.push(yt);
}
ticks.push(y0);
}
return ticks
},
}],
xAxes: [{
scaleLabel: false,//{display: true, labelString: x_label},
type: 'time',
time: {
displayFormats: {'millisecond': 'HH:mm:ss.SSS', 'second': 'HH:mm:ss', 'minute': 'HH:mm','hour': 'dd HH:mm', 'day': 'dd MMM DD', 'week': 'MMM DD', 'month': 'MMM DD'},
},
ticks: { padding: -20,
callback: function(label, index, labels) {
let l = labels.length - 1;
if (index == 0 || index == l) return "";
if (index == 1 || index == l - 1) {
// skip first and / or last label, if too close to the end
let minstep = 0.05 * (labels[l].value - labels[0].value);
if (index == 1) {
if (labels[1].value - labels[0].value < minstep) return "";
} else {
if (labels[l].value - labels[l-1].value < minstep) return "";
}
}
hourofday = /\S+ (\d+:00)/.exec(label);
if (hourofday && hourofday[1] != '00:00') {
return hourofday[1];
}
return label;
let chart_options = {
responsive: true,
maintainAspectRatio: false,
animation:{duration:0},
scales: {
y:{
beginAtZero: false,
ticks:{
mirror: true,
padding: -10,
callback: function(label, index, labels) {
if(index == 0 || index == labels.length-1)
return "";
return strFormat(label);
}
},
grid:{drawTicks:false},
title: false, //Former scaleLabel
type: scaleType,
position: 'right',
afterBuildTicks: function(axis) {
let ticks = axis.ticks
if (scaleType == "logarithmic" && ticks.length <= 4) {
y1 = ticks[0];
y0 = ticks.slice(-1)[0];
span = y1 - y0;
step = Math.abs(span * 0.3).toExponential(0);
if (step[0] > '5') {
step = '5' + step.substr(1);
} else if (step[0] > '2') {
step = '2' + step.substr(1);
}
},
afterBuildTicks: function(axis, ticks) {
if (!ticks || ticks.length <= 2) return ticks;
first = ticks[0].value;
step = ticks[1].value - first;
offset = (first - axis._adapter.startOf(first, 'day')) % step;
let start = 0;
if (ticks[0].value - offset < axis.min) start = 1;
let v = axis.min;
result = [{value: v, major: false}];
for (tick of ticks.slice(start)) {
v = tick.value - offset;
result.push({value: v, major: false});
}
v += step;
if (v < axis.max) result.push({value:v, major: false});
result.push({value: axis.max, major: false});
return result;
},
gridLines:{drawTicks:false},
}],
step = Number.parseFloat(step);
ticks = [y1];
for (let yt = Math.ceil(y1 / step) * step; yt > y0; yt -= step) {
ticks.push(yt);
}
ticks.push(y0);
}
return ticks
},
},
tooltips: false,
legend: false,
pan: {
enabled: true,
mode: 'xy',
speed: 10,
threshold: 10,
onPan: function({chart}) { graphs.panCallback(chart.graph);},
//onPanComplete: function({chart}){graphs.checkReload(chart.graph);redraw()},
onPanComplete: function({chart}){graphs.updateAuto();},
},
zoom: {
enabled: true,
drag: false,
mode: isTouchDevice ? 'xy': 'x',
speed: 0.1,
sensitivity: 1,
onZoom: function({chart}) { graphs.zoomCallback(chart.graph);},
//onZoomComplete: function({chart}){graphs.checkReload(chart.graph);redraw()},
onZoomComplete: function({chart}){graphs.onZoomCompleteCallback()},
x:{
title: false, // former scaleLabel
type: 'time',
time: {
displayFormats: {'millisecond': 'HH:mm:ss.SSS', 'second': 'HH:mm:ss', 'minute': 'HH:mm','hour': 'EEE d. HH:mm', 'day': 'EE d.', 'week': 'd. MMM yy', 'month': 'MMM yy'},
},
ticks: {
padding: -20,
// stepSize: 180000,
autoSkip: true,
maxRotation: 0,
// callback not used, this is better done in afterBuildTicks
},
afterBuildTicks: function(axis) {
let ticks = axis.ticks
if (!ticks || ticks.length <= 2) return ticks;
first = ticks[0].value;
step = ticks[1].value - first;
offset = (first - axis._adapter.startOf(first, 'day')) % step;
let result = [];
let v = axis.min;
for (tick of ticks) {
v = tick.value - offset;
if (v > axis.min + step / 2) {
result.push({value: v, major: false});
}
}
v += step;
if (v < axis.max) result.push({value:v, major: false});
axis.ticks = result;
// return result;
},
beforeFit: function(axis) { // called after ticks are autoskipped
prevday = '';
for (tick of axis.ticks) {
s = tick.label.split(' ');
if (s.length == 3) { // format with day
// show date only on first label of a day
day = s.slice(0, 2).join(' ');
if (day != prevday) {
prevday = day;
} else {
tick.label = s[2]; // time
}
}
}
},
grid:{drawTicks:false},
}
},
plugins:{
tooltip: false,
legend: false,
zoom:{
pan: {
enabled: true,
mode: 'xy',
speed: 10,
threshold: 10,
onPan: function({chart}) { graphs.panCallback(chart.graph);},
onPanComplete: function({chart}){graphs.updateAuto();},
},
zoom: {
wheel:{
enabled: true
},
pinch:{
enabled: true
},
mode: isTouchDevice ? 'xy': 'x',
speed: 0.1,
sensitivity: 1,
onZoom: function({chart}) { graphs.zoomCallback(chart.graph);},
onZoomComplete: function({chart}){graphs.onZoomCompleteCallback()},
}
}
}
});
}
if (gindex != 0) {
// show time labels only on first chart
chart_options.scales.x.ticks.callback = function () { return ' '; }
}
chart = new Chart(ctx, {type: 'scatter', options: chart_options})
//console.log('create legend')
let legend = document.createElement('div');
@ -1802,8 +1839,8 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
* @param {number} max - The maximum timestamp in milliseconds of the viewing window
*/
function setMinMax(min, max){
let ax = chart.options.scales.xAxes[0];
let ay = chart.options.scales.yAxes[0];
let ax = chart.options.scales.x;
let ay = chart.options.scales.y;
// clamp X-span
let span = max - min;
let half = 0;
@ -1818,13 +1855,13 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
mid = (chart.lastXmin + chart.lastXmax) * 0.5;
min = mid - half;
max = mid + half;
ay.ticks.min = chart.lastYmin;
ay.ticks.max = chart.lastYmax;
ay.min = chart.lastYmin;
ay.max = chart.lastYmax;
} else {
chart.lastXmin = min;
chart.lastXmax = max;
chart.lastYmin = ay.ticks.min;
chart.lastYmax = ay.ticks.max;
chart.lastYmin = ay.min;
chart.lastYmax = ay.max;
}
// custom algorithm for tick step
mainstep = 1000;
@ -1844,13 +1881,13 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
}
mainstep *= info[1];
}
ax.time.unit = info[0];
ax.time.stepSize = Math.round(step / mainstep);
//ax.ticks.unit = ax.time.unit;
//ax.ticks.stepSize =ax.time.stepSize;
//console.log('INFO', step, mainstep, info, ax, ax.time);
ax.ticks.max = max;
ax.ticks.min = min;
unit = info[0];
rstep = Math.round(step / mainstep);
ax.time.unit = unit;
ax.ticks.stepSize = rstep;
ax.max = max;
ax.min = min;
}
/**
@ -1873,7 +1910,7 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
* Called when log button in the legend is clicked
*/
function toggleAxesType(){
setAxesType((chart.options.scales.yAxes[0].type=== 'linear') ? 'logarithmic' : 'linear');
setAxesType((chart.options.scales.y.type=== 'linear') ? 'logarithmic' : 'linear');
}
/**
@ -1887,7 +1924,7 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
}else{
linlog.innerHTML = "<strong>&#9746;</strong> log";
}
chart.options.scales.yAxes[0].type = type;
chart.options.scales.y.type = type;
chart.options.animation.duration = 800;
if (autoScaleFlag) graphs.autoScale(chart);
update();
@ -1905,8 +1942,8 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
let y = null;
for(let j = 0; j < chart.getDatasetMeta(i).data.length; j++){
let dp = chart.getDatasetMeta(i).data[j];
if (dp._model.x >= x) break;
y = chart.data.datasets[i].data[dp._index].y;
if (dp.x >= x) break; //_model does not exist anymore, properties are defined directly on elements
y = chart.data.datasets[i].data[dp.$context.index].y; // $context not mentionned in ChartJS doc, seen with console.log
}
valueElm = legendvalues[chart.data.datasets[i].key];
if (labelMinWidth == 0) {
@ -1996,6 +2033,7 @@ function updateCharts2(graph){
console.log('graphs.doUpdates skipped');
return;
}
//console.log('G', graph);
for(let key in graph){
if (graph[key][0] != null) {
// there is at least ONE valid datapoint

View File

@ -76,7 +76,7 @@ new Settings()
.treat("showConsole", "sc", to_bool, true)
.treat("showOverview", "so", to_bool, true)
.treat("showGraphics", "sg", to_bool, true) // false)
.treat("hideRightPart", "hr", to_bool, false) //used to completely disable the right part
.treat("hideRightPart", "hr", to_bool, true) //used to completely disable the right part
.treat("wideGraphs", "wg", to_bool, false) //used to toggle the size of the graphs part
.treat("showAsync", "sa", to_bool, false)

View File

@ -190,7 +190,7 @@ class InfluxGraph:
"""
Polls the last known values for all the available variables, and returns only those whose polled values are more recent than the most recent displayed one.
Every plain minute, all the variables are returned with a point having their last known value at the current timestamp to synchronize all the curves on the GUI.
Returns :
{"type":"graph-update", "time":(int), "graph":{(str):[[(int),(float)]]}} | None : a dictionnary with its "graph-update" type
(so it can be processed by the client), and a "graph" dictionnary with the variable names as key, and an array of points, which are an array containing the timestamp
@ -201,21 +201,15 @@ class InfluxGraph:
now, = self.get_abs_time([0])
result = self.influx_data_getter.poll_last_values(list(self.variables.keys()), self.lastvalues, now)
for variable in self.lastvalues.keys():
if variable in result.keys():
# removes points older than the last known point (queries are in seconds and might return points already displayed)
while len(result[variable]) > 0:
if result[variable][0][0] <= self.lastvalues[variable][0]:
result[variable].pop(0)
else:
break
if len(result[variable]) > 0 and result[variable][-1][0] > self.lastvalues[variable][0]:
self.lastvalues[variable] = (result[variable][-1][0], result[variable][-1][1])
else:
del result[variable]
for variable, values in list(result.items()):
tlast = self.lastvalues.get(variable, (0,))[0]
# removes points older than the last known point (queries are in seconds and might return points already displayed)
while values and values[0][0] <= tlast:
values.pop(0)
if values and values[-1][0] > tlast:
self.lastvalues[variable] = values[-1]
else:
del result[variable]
if int(now / 60) != int(self.end_query / 60):
# Update unchanged values every plain minute
for var, (_, lastx) in self.lastvalues.items():
@ -224,4 +218,4 @@ class InfluxGraph:
self.end_query = now
if len(result) > 0:
return dict(type='graph-update', time=now, graph=result)
return None
return None

View File

@ -82,7 +82,7 @@ def get_update(path=None):
yield to_json_sse(msg)
if messages:
lastmsg = time.time()
gevent.sleep(0.1)
gevent.sleep(1)
else:
if time.time() > lastmsg + 30:
if not client.info():