dashboard: toggle SSE persistent connections based on document visibility

While SSE promises to be properly multiplexed over HTTP/2, it seems browsers
still hit a hard limit when opening >6 connections. Since the streams are not
critical, it's fine to abstract the eventsource logic to the base widget class
where it manages open/closing connections based on tab visiblity.

If we need more than 6 parallel streams, we need to revamp the logic
to multiplex multiple event types in the backend ourselves, but this is not
necessary for now.
This commit is contained in:
Stephan de Wit 2024-06-07 16:28:04 +02:00
parent 341a4ce8c9
commit a404e9c5a2
5 changed files with 50 additions and 27 deletions

View File

@ -188,6 +188,10 @@ class WidgetManager {
widget.setId(id);
this.widgetClasses[id] = widget;
document.addEventListener('visibilitychange', (e) => {
this.widgetClasses[id].onVisibilityChanged(!document.hidden);
});
if (!id in this.widgetTranslations) {
console.error('Missing translations for widget', id);
}

View File

@ -31,6 +31,9 @@ export default class BaseWidget {
this.translations = {};
this.tickTimeout = 5000; // Default tick timeout
this.resizeHandles = "all"
this.eventSource = null;
this.eventSourceUrl = null;
this.eventSourceOnData = null;
}
getResizeHandles() {
@ -67,7 +70,40 @@ export default class BaseWidget {
}
onWidgetClose() {
return null;
this.closeEventSource();
}
openEventSource(url, onMessage) {
this.closeEventSource();
this.eventSourceUrl = url;
this.eventSourceOnData = onMessage;
this.eventSource = new EventSource(url);
this.eventSource.onmessage = onMessage;
this.eventSource.onerror = (e) => {
if (this.eventSource.readyState == EventSource.closed) {
this.closeEventSource();
} else {
console.error('Unknown error occurred during streaming operation', e);
}
};
}
closeEventSource() {
if (this.eventSource !== null) {
this.eventSource.close();
this.eventSource = null;
}
}
onVisibilityChanged(visible) {
if (this.eventSourceUrl !== null) {
if (visible) {
this.openEventSource(this.eventSourceUrl, this.eventSourceOnData);
} else if (this.eventSource !== null) {
this.closeEventSource();
}
}
}
/* For testing purposes */

View File

@ -32,7 +32,6 @@ export default class Cpu extends BaseWidget {
constructor() {
super();
this.resizeHandles = "e, w";
this.eventSource = null;
}
_createChart(selector, timeSeries) {
@ -85,12 +84,6 @@ export default class Cpu extends BaseWidget {
return $container;
}
onwidgetClose() {
if (this.eventSource !== null) {
this.eventSource.close();
}
}
async onMarkupRendered() {
ajaxGet('/api/diagnostics/cpu_usage/getcputype', {}, (data, status) => {
$('.cpu-type').text(data);
@ -104,11 +97,9 @@ export default class Cpu extends BaseWidget {
this._createChart('cpu-usage-intr', intr_ts);
this._createChart('cpu-usage-user', user_ts);
this._createChart('cpu-usage-sys', sys_ts);
this.eventSource = new EventSource('/api/diagnostics/cpu_usage/stream');
this.eventSource.onmessage = function(event) {
super.openEventSource('/api/diagnostics/cpu_usage/stream', (event) => {
if (!event) {
this.eventSource.close();
super.closeEventSource();
}
const data = JSON.parse(event.data);
let date = Date.now();
@ -116,7 +107,7 @@ export default class Cpu extends BaseWidget {
intr_ts.append(date, data.intr);
user_ts.append(date, data.user);
sys_ts.append(date, data.sys);
};
});
}
onWidgetResize(elem, width, height) {

View File

@ -33,7 +33,6 @@ export default class Firewall extends BaseTableWidget {
constructor(config) {
super();
this.config = config;
this.eventSource = null;
this.ifMap = {};
this.counters = {};
this.chart = null;
@ -86,7 +85,7 @@ export default class Firewall extends BaseTableWidget {
_onMessage(event) {
if (!event) {
this.eventSource.close();
super.closeEventSource();
}
let actIcons = {
@ -190,8 +189,7 @@ export default class Firewall extends BaseTableWidget {
return;
}
this.eventSource = new EventSource('/api/diagnostics/firewall/streamLog');
this.eventSource.onmessage = this._onMessage.bind(this);
super.openEventSource('/api/diagnostics/firewall/streamLog', this._onMessage.bind(this));
let context = document.getElementById('fw-chart').getContext('2d');
let config = {
@ -285,9 +283,7 @@ export default class Firewall extends BaseTableWidget {
}
onWidgetClose() {
if (this.eventSource !== null) {
this.eventSource.close();
}
super.onWidgetClose();
if (this.chart !== null) {
this.chart.destroy();

View File

@ -37,7 +37,6 @@ export default class Traffic extends BaseWidget {
trafficOut: null
};
this.initialized = false;
this.eventSource = null;
this.datasets = {inbytes: [], outbytes: []};
}
@ -155,7 +154,7 @@ export default class Traffic extends BaseWidget {
_onMessage(event) {
if (!event) {
this.eventSource.close();
super.closeEventSource();
}
const data = JSON.parse(event.data);
@ -199,15 +198,12 @@ export default class Traffic extends BaseWidget {
}
async onMarkupRendered() {
this.eventSource = new EventSource('/api/diagnostics/traffic/stream/2');
this.eventSource.onmessage = this._onMessage.bind(this);
super.openEventSource('/api/diagnostics/traffic/stream/1', this._onMessage.bind(this));
}
onWidgetClose() {
super.onWidgetClose();
this.charts.trafficIn.destroy();
this.charts.trafficOut.destroy();
if (this.eventSource !== null) {
this.eventSource.close();
}
}
}