summaryrefslogtreecommitdiffstats
path: root/modules/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/include/15_traffic.js
blob: 29e7874a3c915d6778c9f122c1764bacc78f4ff7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
'use strict';
'require baseclass';
'require view.dashboard.lib.charts as charts';
'require view.dashboard.lib.history as history';

const NET = 4, WAN = 5;

function rate(cur, prev, seconds, index) {
	const name = cur?.[WAN];
	if (!name || name !== prev?.[WAN])
		return null;

	const a = prev?.[NET]?.[name];
	const b = cur?.[NET]?.[name];

	if (!Array.isArray(a) || !Array.isArray(b) || !Number.isFinite(seconds) || seconds <= 0)
		return null;

	// Do not calculate rates across a device recreation or an uncertain read.
	if (a[2] == null || a[2] !== b[2])
		return null;

	for (let i = 0; i < 2; i++)
		if (!Number.isFinite(a[i]) || !Number.isFinite(b[i]) || a[i] < 0 || b[i] < a[i])
			return null;

	return (b[index] - a[index]) * 8 / seconds;
}

function points(index) {
	const points = history.series((cur, prev, seconds) => rate(cur, prev, seconds, index));
	const last = points[points.length - 1];
	if (last && history.now - last.t > history.step * 2.5)
		points.push({ t: history.now, v: null });
	return points;
}

return baseclass.extend({
	title: _('WAN traffic'),
	widgets: [
		{ id: 'wan', slot: 'charts', title: _('WAN traffic'), order: 10 }
	],

	load() {
		return history.load();
	},

	render() {
		const down = points(0);
		const up = points(1);
		const last = points => points.length ? points[points.length - 1].v : null;
		const scale = charts.rateScale(Math.max(1, ...down.concat(up).map(p => p.v || 0)));
		const available = down.some(p => p.v != null) || up.some(p => p.v != null);
		const current = last(down) != null && last(up) != null;
		const sample = history.samples[history.samples.length - 1];
		const device = sample?.[WAN];
		const body = [];

		if (available) {
			body.push(this.plot = charts.lines({
				node: this.plot,
				span: history.span(),
				max: scale.max,
				ticks: scale.ticks,
				ariaLabel: this.title,
				series: [ { values: down, area: true }, { values: up, area: true } ]
			}), charts.legend([
				{ className: 'dashboard-series-1', label: _('Download'), value: scale.format(last(down)) },
				{ className: 'dashboard-series-2', label: _('Upload'), value: scale.format(last(up)) }
			]));
		}
		if (!current)
			body.push(charts.empty(history.failed ? _('No data received')
				: !sample ? history.status() : !device ? _('Not connected') : available ? _('Waiting for a new sample...') : history.status()));

		// No null children: older LuCI DOM helpers render them as literal text.
		return { charts: [ { id: 'wan', node: this.chart = charts.card({
			node: this.chart,
			title: this.title,
			desc: device ? device + ' · ' + scale.unit : scale.unit,
			body: body
		}) } ] };
	}
});