9 var callLuciRealtimeStats
= rpc
.declare({
11 method
: 'getRealtimeStats',
12 params
: [ 'mode', 'device' ],
13 expect
: { result
: [] }
19 Math
.log2
= Math
.log2
|| function(x
) { return Math
.log(x
) * Math
.LOG2E
; };
24 this.loadSVG(L
.resource('svg/wireless.svg')),
25 this.loadSVG(L
.resource('svg/wifirate.svg')),
26 network
.getWifiDevices().then(function(radios
) {
27 var tasks
= [], all_networks
= [];
29 for (var i
= 0; i
< radios
.length
; i
++)
30 tasks
.push(radios
[i
].getWifiNetworks().then(function(networks
) {
31 all_networks
.push
.apply(all_networks
, networks
);
34 return Promise
.all(tasks
).then(function() {
41 updateGraph: function(ifname
, svg
, lines
, cb
) {
42 var G
= svg
.firstElementChild
;
44 var view
= document
.querySelector('#view');
46 var width
= view
.offsetWidth
- 2;
50 var data_wanted
= Math
.floor(width
/ step
);
55 for (var i
= 0; i
< lines
.length
; i
++)
65 /* prefill datasets */
66 for (var i
= 0; i
< data_values
.length
; i
++)
67 for (var j
= 0; j
< data_wanted
; j
++)
68 data_values
[i
][j
] = 0;
70 /* plot horizontal time interval lines */
71 for (var i
= width
% (step
* 60); i
< width
; i
+= step
* 60) {
72 var line
= document
.createElementNS('http://www.w3.org/2000/svg', 'line');
73 line
.setAttribute('x1', i
);
74 line
.setAttribute('y1', 0);
75 line
.setAttribute('x2', i
);
76 line
.setAttribute('y2', '100%');
77 line
.setAttribute('style', 'stroke:black;stroke-width:0.1');
79 var text
= document
.createElementNS('http://www.w3.org/2000/svg', 'text');
80 text
.setAttribute('x', i
+ 5);
81 text
.setAttribute('y', 15);
82 text
.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
83 text
.appendChild(document
.createTextNode(Math
.round((width
- i
) / step
/ 60) + 'm'));
89 info
.interval
= pollInterval
;
90 info
.timeframe
= data_wanted
/ 60;
107 pollData: function() {
108 poll
.add(L
.bind(function() {
111 for (var i
= 0; i
< graphPolls
.length
; i
++) {
112 var ctx
= graphPolls
[i
];
113 tasks
.push(L
.resolveDefault(callLuciRealtimeStats('wireless', ctx
.ifname
), []));
116 return Promise
.all(tasks
).then(L
.bind(function(datasets
) {
117 for (var gi
= 0; gi
< graphPolls
.length
; gi
++) {
118 var ctx
= graphPolls
[gi
],
125 var data_wanted
= Math
.floor(ctx
.width
/ ctx
.step
);
126 var last_timestamp
= NaN
;
128 for (var i
= 0, di
= 0; di
< lines
.length
; di
++) {
129 if (lines
[di
] == null)
132 var multiply
= (lines
[di
].multiply
!= null) ? lines
[di
].multiply
: 1,
133 offset
= (lines
[di
].offset
!= null) ? lines
[di
].offset
: 0;
135 for (var j
= ctx
.timestamp
? 0 : 1; j
< data
.length
; j
++) {
136 /* skip overlapping entries */
137 if (data
[j
][0] <= ctx
.timestamp
)
142 last_timestamp
= data
[j
][0];
145 info
.line_current
[i
] = data
[j
][di
+ 1] * multiply
;
146 info
.line_current
[i
] -= Math
.min(info
.line_current
[i
], offset
);
147 values
[i
].push(info
.line_current
[i
]);
153 /* cut off outdated entries */
154 ctx
.fill
= Math
.min(ctx
.fill
, data_wanted
);
156 for (var i
= 0; i
< values
.length
; i
++) {
157 var len
= values
[i
].length
;
158 values
[i
] = values
[i
].slice(len
- data_wanted
, len
);
160 /* find peaks, averages */
161 info
.line_peak
[i
] = NaN
;
162 info
.line_average
[i
] = 0;
164 for (var j
= 0; j
< values
[i
].length
; j
++) {
165 info
.line_peak
[i
] = isNaN(info
.line_peak
[i
]) ? values
[i
][j
] : Math
.max(info
.line_peak
[i
], values
[i
][j
]);
166 info
.line_average
[i
] += values
[i
][j
];
169 info
.line_average
[i
] = info
.line_average
[i
] / ctx
.fill
;
172 info
.peak
= Math
.max
.apply(Math
, info
.line_peak
);
174 /* remember current timestamp, calculate horizontal scale */
175 if (!isNaN(last_timestamp
))
176 ctx
.timestamp
= last_timestamp
;
178 var size
= Math
.floor(Math
.log2(info
.peak
)),
179 div
= Math
.pow(2, size
- (size
% 10)),
180 mult
= info
.peak
/ div
,
181 mult
= (mult
< 5) ? 2 : ((mult
< 50) ? 10 : ((mult
< 500) ? 100 : 1000));
183 info
.peak
= info
.peak
+ (mult
* div
) - (info
.peak
% (mult
* div
));
185 data_scale
= ctx
.height
/ info
.peak
;
188 for (var i
= 0, di
= 0; di
< lines
.length
; di
++) {
189 if (lines
[di
] == null)
192 var el
= ctx
.svg
.firstElementChild
.getElementById(lines
[di
].line
),
193 pt
= '0,' + ctx
.height
,
199 for (var j
= 0; j
< values
[i
].length
; j
++) {
200 var x
= j
* ctx
.step
;
202 y
= ctx
.height
- Math
.floor(values
[i
][j
] * data_scale
);
203 //y -= Math.floor(y % (1 / data_scale));
205 y
= isNaN(y
) ? ctx
.height
: y
;
207 pt
+= ' ' + x
+ ',' + y
;
210 pt
+= ' ' + ctx
.width
+ ',' + y
+ ' ' + ctx
.width
+ ',' + ctx
.height
;
212 el
.setAttribute('points', pt
);
217 info
.label_25
= 0.25 * info
.peak
;
218 info
.label_50
= 0.50 * info
.peak
;
219 info
.label_75
= 0.75 * info
.peak
;
221 if (typeof(ctx
.cb
) == 'function')
222 ctx
.cb(ctx
.svg
, info
);
225 }, this), pollInterval
);
228 loadSVG: function(src
) {
229 return request
.get(src
).then(function(response
) {
231 throw new Error(response
.statusText
);
234 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff'
235 }, E(response
.text()));
239 render: function(data
) {
244 var v
= E('div', {}, E('div'));
246 for (var i
= 0; i
< wifidevs
.length
; i
++) {
247 var ifname
= wifidevs
[i
].getIfname();
252 var csvg1
= svg1
.cloneNode(true),
253 csvg2
= svg2
.cloneNode(true);
255 v
.firstElementChild
.appendChild(E('div', { 'data-tab': ifname
, 'data-tab-title': ifname
}, [
257 E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')),
260 E('table', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
261 E('tr', { 'class': 'tr' }, [
262 E('td', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid blue' }, [ _('Signal:') ])),
263 E('td', { 'class': 'td', 'id': 'rssi_bw_cur' }, [ '0 ' + _('dBm') ]),
265 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
266 E('td', { 'class': 'td', 'id': 'rssi_bw_avg' }, [ '0 ' + _('dBm') ]),
268 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
269 E('td', { 'class': 'td', 'id': 'rssi_bw_peak' }, [ '0 ' + _('dBm') ])
271 E('tr', { 'class': 'tr' }, [
272 E('td', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid red' }, [ _('Noise:') ])),
273 E('td', { 'class': 'td', 'id': 'noise_bw_cur' }, [ '0 ' + _('dBm') ]),
275 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
276 E('td', { 'class': 'td', 'id': 'noise_bw_avg' }, [ '0 ' + _('dBm') ]),
278 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
279 E('td', { 'class': 'td', 'id': 'noise_bw_peak' }, [ '0 ' + _('dBm') ])
285 E('div', { 'class': 'right' }, E('small', { 'id': 'scale2' }, '-')),
288 E('table', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
289 E('tr', { 'class': 'tr' }, [
290 E('td', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid green' }, [ _('Phy Rate:') ])),
291 E('td', { 'class': 'td', 'id': 'rate_bw_cur' }, [ '0 Mbit/s' ]),
293 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
294 E('td', { 'class': 'td', 'id': 'rate_bw_avg' }, [ '0 Mbit/s' ]),
296 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
297 E('td', { 'class': 'td', 'id': 'rate_bw_peak' }, [ '0 Mbit/s' ])
302 this.updateGraph(ifname
, csvg1
, [ null, { line
: 'rssi', offset
: 155 }, { line
: 'noise', offset
: 155 } ], function(svg
, info
) {
303 var G
= svg
.firstElementChild
, tab
= svg
.parentNode
;
305 G
.getElementById('label_25').firstChild
.data
= '%d %s'.format(info
.label_25
- 100, _('dBm'));
306 G
.getElementById('label_50').firstChild
.data
= '%d %s'.format(info
.label_50
- 100, _('dBm'));
307 G
.getElementById('label_75').firstChild
.data
= '%d %s'.format(info
.label_75
- 100, _('dBm'));
309 tab
.querySelector('#scale').firstChild
.data
= _('(%d minute window, %d second interval)').format(info
.timeframe
, info
.interval
);
311 tab
.querySelector('#rssi_bw_cur').firstChild
.data
= '%d %s'.format(info
.line_current
[0] - 100, _('dBm'));
312 tab
.querySelector('#rssi_bw_avg').firstChild
.data
= '%d %s'.format(info
.line_average
[0] - 100, _('dBm'));
313 tab
.querySelector('#rssi_bw_peak').firstChild
.data
= '%d %s'.format(info
.line_peak
[0] - 100, _('dBm'));
315 tab
.querySelector('#noise_bw_cur').firstChild
.data
= '%d %s'.format(info
.line_current
[1] - 100, _('dBm'));
316 tab
.querySelector('#noise_bw_avg').firstChild
.data
= '%d %s'.format(info
.line_average
[1] - 100, _('dBm'));
317 tab
.querySelector('#noise_bw_peak').firstChild
.data
= '%d %s'.format(info
.line_peak
[1] - 100, _('dBm'));
320 this.updateGraph(ifname
, csvg2
, [ { line
: 'rate', multiply
: 0.001 } ], function(svg
, info
) {
321 var G
= svg
.firstElementChild
, tab
= svg
.parentNode
;
323 G
.getElementById('label_25').firstChild
.data
= '%.2f %s'.format(info
.label_25
, _('Mbit/s'));
324 G
.getElementById('label_50').firstChild
.data
= '%.2f %s'.format(info
.label_50
, _('Mbit/s'));
325 G
.getElementById('label_75').firstChild
.data
= '%.2f %s'.format(info
.label_75
, _('Mbit/s'));
327 tab
.querySelector('#scale2').firstChild
.data
= _('(%d minute window, %d second interval)').format(info
.timeframe
, info
.interval
);
329 tab
.querySelector('#rate_bw_cur').firstChild
.data
= '%d %s'.format(info
.line_current
[0], _('Mbit/s'));
330 tab
.querySelector('#rate_bw_avg').firstChild
.data
= '%d %s'.format(info
.line_average
[0], _('Mbit/s'));
331 tab
.querySelector('#rate_bw_peak').firstChild
.data
= '%d %s'.format(info
.line_peak
[0], _('Mbit/s'));
335 ui
.tabs
.initTabGroup(v
.firstElementChild
.childNodes
);
340 E('h2', _('Wireless')),
341 E('div', {'class': 'cbi-map-descr'}, _('This page displays the wireless metrics, for each available radio interfaces.')),
346 handleSaveApply
: null,