7 var callLuciRealtimeStats
= rpc
.declare({
9 method
: 'getRealtimeStats',
10 params
: [ 'mode', 'device' ],
11 expect
: { result
: [] }
14 var callLuciConntrackList
= rpc
.declare({
16 method
: 'getConntrackList',
17 expect
: { result
: [] }
20 var callNetworkRrdnsLookup
= rpc
.declare({
21 object
: 'network.rrdns',
23 params
: [ 'addrs', 'timeout', 'limit' ],
30 enableLookups
= false;
32 var recheck_lookup_queue
= {};
34 Math
.log2
= Math
.log2
|| function(x
) { return Math
.log(x
) * Math
.LOG2E
; };
39 this.loadSVG(L
.resource('svg/connections.svg'))
43 updateGraph: function(svg
, lines
, cb
) {
44 var G
= svg
.firstElementChild
;
46 var view
= document
.querySelector('#view');
48 var width
= view
.offsetWidth
- 2;
52 var data_wanted
= Math
.floor(width
/ step
);
57 for (var i
= 0; i
< lines
.length
; i
++)
67 /* prefill datasets */
68 for (var i
= 0; i
< data_values
.length
; i
++)
69 for (var j
= 0; j
< data_wanted
; j
++)
70 data_values
[i
][j
] = 0;
72 /* plot horizontal time interval lines */
73 for (var i
= width
% (step
* 60); i
< width
; i
+= step
* 60) {
74 var line
= document
.createElementNS('http://www.w3.org/2000/svg', 'line');
75 line
.setAttribute('x1', i
);
76 line
.setAttribute('y1', 0);
77 line
.setAttribute('x2', i
);
78 line
.setAttribute('y2', '100%');
79 line
.setAttribute('style', 'stroke:black;stroke-width:0.1');
81 var text
= document
.createElementNS('http://www.w3.org/2000/svg', 'text');
82 text
.setAttribute('x', i
+ 5);
83 text
.setAttribute('y', 15);
84 text
.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
85 text
.appendChild(document
.createTextNode(Math
.round((width
- i
) / step
/ 60) + 'm'));
91 info
.interval
= pollInterval
;
92 info
.timeframe
= data_wanted
/ 60;
108 updateConntrack: function(conn
) {
109 var lookup_queue
= [ ];
112 conn
.sort(function(a
, b
) {
113 return b
.bytes
- a
.bytes
;
116 for (var i
= 0; i
< conn
.length
; i
++)
120 if ((c
.src
== '127.0.0.1' && c
.dst
== '127.0.0.1') ||
121 (c
.src
== '::1' && c
.dst
== '::1'))
124 if (!dns_cache
[c
.src
] && lookup_queue
.indexOf(c
.src
) == -1)
125 lookup_queue
.push(c
.src
);
127 if (!dns_cache
[c
.dst
] && lookup_queue
.indexOf(c
.dst
) == -1)
128 lookup_queue
.push(c
.dst
);
130 var src
= dns_cache
[c
.src
] || (c
.layer3
== 'ipv6' ? '[' + c
.src
+ ']' : c
.src
);
131 var dst
= dns_cache
[c
.dst
] || (c
.layer3
== 'ipv6' ? '[' + c
.dst
+ ']' : c
.dst
);
134 c
.layer3
.toUpperCase(),
135 c
.layer4
.toUpperCase(),
136 '%h'.format(c
.hasOwnProperty('sport') ? (src
+ ':' + c
.sport
) : src
),
137 '%h'.format(c
.hasOwnProperty('dport') ? (dst
+ ':' + c
.dport
) : dst
),
138 '%1024.2mB (%d %s)'.format(c
.bytes
, c
.packets
, _('Pkts.'))
142 cbi_update_table('#connections', rows
, E('em', _('No information available')));
144 if (enableLookups
&& lookup_queue
.length
> 0) {
145 var reduced_lookup_queue
= lookup_queue
;
147 if (lookup_queue
.length
> 100)
148 reduced_lookup_queue
= lookup_queue
.slice(0, 100);
150 callNetworkRrdnsLookup(reduced_lookup_queue
, 5000, 1000).then(function(replies
) {
151 for (var index
in reduced_lookup_queue
) {
152 var address
= reduced_lookup_queue
[index
];
157 if (replies
[address
]) {
158 dns_cache
[address
] = replies
[address
];
159 lookup_queue
.splice(reduced_lookup_queue
.indexOf(address
), 1);
163 if (recheck_lookup_queue
[address
] > 2) {
164 dns_cache
[address
] = (address
.match(/:/)) ? '[' + address
+ ']' : address
;
165 lookup_queue
.splice(index
, 1);
168 recheck_lookup_queue
[address
] = (recheck_lookup_queue
[address
] || 0) + 1;
172 var btn
= document
.querySelector('.btn.toggle-lookups');
174 btn
.firstChild
.data
= enableLookups
? _('Disable DNS lookups') : _('Enable DNS lookups');
175 btn
.classList
.remove('spinning');
176 btn
.disabled
= false;
182 pollData: function() {
183 poll
.add(L
.bind(function() {
185 L
.resolveDefault(callLuciConntrackList(), [])
188 for (var i
= 0; i
< graphPolls
.length
; i
++) {
189 var ctx
= graphPolls
[i
];
190 tasks
.push(L
.resolveDefault(callLuciRealtimeStats('conntrack'), []));
193 return Promise
.all(tasks
).then(L
.bind(function(datasets
) {
194 this.updateConntrack(datasets
[0]);
196 for (var gi
= 0; gi
< graphPolls
.length
; gi
++) {
197 var ctx
= graphPolls
[gi
],
198 data
= datasets
[gi
+ 1],
204 var data_wanted
= Math
.floor(ctx
.width
/ ctx
.step
);
205 var last_timestamp
= NaN
;
207 for (var i
= 0, di
= 0; di
< lines
.length
; di
++) {
208 if (lines
[di
] == null)
211 var multiply
= (lines
[di
].multiply
!= null) ? lines
[di
].multiply
: 1,
212 offset
= (lines
[di
].offset
!= null) ? lines
[di
].offset
: 0;
214 for (var j
= ctx
.timestamp
? 0 : 1; j
< data
.length
; j
++) {
215 /* skip overlapping entries */
216 if (data
[j
][0] <= ctx
.timestamp
)
221 last_timestamp
= data
[j
][0];
224 info
.line_current
[i
] = data
[j
][di
+ 1] * multiply
;
225 info
.line_current
[i
] -= Math
.min(info
.line_current
[i
], offset
);
226 values
[i
].push(info
.line_current
[i
]);
232 /* cut off outdated entries */
233 ctx
.fill
= Math
.min(ctx
.fill
, data_wanted
);
235 for (var i
= 0; i
< values
.length
; i
++) {
236 var len
= values
[i
].length
;
237 values
[i
] = values
[i
].slice(len
- data_wanted
, len
);
239 /* find peaks, averages */
240 info
.line_peak
[i
] = NaN
;
241 info
.line_average
[i
] = 0;
243 for (var j
= 0; j
< values
[i
].length
; j
++) {
244 info
.line_peak
[i
] = isNaN(info
.line_peak
[i
]) ? values
[i
][j
] : Math
.max(info
.line_peak
[i
], values
[i
][j
]);
245 info
.line_average
[i
] += values
[i
][j
];
248 info
.line_average
[i
] = info
.line_average
[i
] / ctx
.fill
;
251 info
.peak
= Math
.max
.apply(Math
, info
.line_peak
);
253 /* remember current timestamp, calculate horizontal scale */
254 if (!isNaN(last_timestamp
))
255 ctx
.timestamp
= last_timestamp
;
257 var size
= Math
.floor(Math
.log2(info
.peak
)),
258 div
= Math
.pow(2, size
- (size
% 10)),
259 mult
= info
.peak
/ div
,
260 mult
= (mult
< 5) ? 2 : ((mult
< 50) ? 10 : ((mult
< 500) ? 100 : 1000));
262 info
.peak
= info
.peak
+ (mult
* div
) - (info
.peak
% (mult
* div
));
264 data_scale
= ctx
.height
/ info
.peak
;
267 for (var i
= 0, di
= 0; di
< lines
.length
; di
++) {
268 if (lines
[di
] == null)
271 var el
= ctx
.svg
.firstElementChild
.getElementById(lines
[di
].line
),
272 pt
= '0,' + ctx
.height
,
278 for (var j
= 0; j
< values
[i
].length
; j
++) {
279 var x
= j
* ctx
.step
;
281 y
= ctx
.height
- Math
.floor(values
[i
][j
] * data_scale
);
282 //y -= Math.floor(y % (1 / data_scale));
284 y
= isNaN(y
) ? ctx
.height
: y
;
286 pt
+= ' ' + x
+ ',' + y
;
289 pt
+= ' ' + ctx
.width
+ ',' + y
+ ' ' + ctx
.width
+ ',' + ctx
.height
;
291 el
.setAttribute('points', pt
);
296 info
.label_25
= 0.25 * info
.peak
;
297 info
.label_50
= 0.50 * info
.peak
;
298 info
.label_75
= 0.75 * info
.peak
;
300 if (typeof(ctx
.cb
) == 'function')
301 ctx
.cb(ctx
.svg
, info
);
304 }, this), pollInterval
);
307 loadSVG: function(src
) {
308 return request
.get(src
).then(function(response
) {
310 throw new Error(response
.statusText
);
313 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff'
314 }, E(response
.text()));
318 render: function(data
) {
323 E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')),
326 E('table', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
327 E('tr', { 'class': 'tr' }, [
328 E('td', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid blue' }, [ _('UDP:') ])),
329 E('td', { 'class': 'td', 'id': 'lb_udp_cur' }, [ '0' ]),
331 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
332 E('td', { 'class': 'td', 'id': 'lb_udp_avg' }, [ '0' ]),
334 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
335 E('td', { 'class': 'td', 'id': 'lb_udp_peak' }, [ '0' ])
337 E('tr', { 'class': 'tr' }, [
338 E('td', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid green' }, [ _('TCP:') ])),
339 E('td', { 'class': 'td', 'id': 'lb_tcp_cur' }, [ '0' ]),
341 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
342 E('td', { 'class': 'td', 'id': 'lb_tcp_avg' }, [ '0' ]),
344 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
345 E('td', { 'class': 'td', 'id': 'lb_tcp_peak' }, [ '0' ])
347 E('tr', { 'class': 'tr' }, [
348 E('td', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid red' }, [ _('Other:') ])),
349 E('td', { 'class': 'td', 'id': 'lb_otr_cur' }, [ '0' ]),
351 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
352 E('td', { 'class': 'td', 'id': 'lb_otr_avg' }, [ '0' ]),
354 E('td', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
355 E('td', { 'class': 'td', 'id': 'lb_otr_peak' }, [ '0' ])
359 E('div', { 'class': 'right' }, [
361 'class': 'btn toggle-lookups',
362 'click': function(ev
) {
363 if (!enableLookups
) {
364 ev
.currentTarget
.classList
.add('spinning');
365 ev
.currentTarget
.disabled
= true;
366 enableLookups
= true;
369 ev
.currentTarget
.firstChild
.data
= _('Enable DNS lookups');
370 enableLookups
= false;
375 }, [ enableLookups
? _('Disable DNS lookups') : _('Enable DNS lookups') ])
380 E('div', { 'class': 'cbi-section-node' }, [
381 E('table', { 'class': 'table', 'id': 'connections' }, [
382 E('tr', { 'class': 'tr table-titles' }, [
383 E('th', { 'class': 'th col-2 hide-xs' }, [ _('Network') ]),
384 E('th', { 'class': 'th col-2' }, [ _('Protocol') ]),
385 E('th', { 'class': 'th col-7' }, [ _('Source') ]),
386 E('th', { 'class': 'th col-7' }, [ _('Destination') ]),
387 E('th', { 'class': 'th col-4' }, [ _('Transfer') ])
389 E('tr', { 'class': 'tr placeholder' }, [
390 E('td', { 'class': 'td' }, [
391 E('em', {}, [ _('Collecting data...') ])
398 this.updateGraph(svg
, [ { line
: 'udp' }, { line
: 'tcp' }, { line
: 'other' } ], function(svg
, info
) {
399 var G
= svg
.firstElementChild
, tab
= svg
.parentNode
;
401 G
.getElementById('label_25').firstChild
.data
= '%d'.format(info
.label_25
);
402 G
.getElementById('label_50').firstChild
.data
= '%d'.format(info
.label_50
);
403 G
.getElementById('label_75').firstChild
.data
= '%d'.format(info
.label_75
);
405 tab
.querySelector('#scale').firstChild
.data
= _('(%d minute window, %d second interval)').format(info
.timeframe
, info
.interval
);
407 tab
.querySelector('#lb_udp_cur').firstChild
.data
= '%d'.format(info
.line_current
[0]);
408 tab
.querySelector('#lb_udp_avg').firstChild
.data
= '%d'.format(info
.line_average
[0]);
409 tab
.querySelector('#lb_udp_peak').firstChild
.data
= '%d'.format(info
.line_peak
[0]);
411 tab
.querySelector('#lb_tcp_cur').firstChild
.data
= '%d'.format(info
.line_current
[1]);
412 tab
.querySelector('#lb_tcp_avg').firstChild
.data
= '%d'.format(info
.line_average
[1]);
413 tab
.querySelector('#lb_tcp_peak').firstChild
.data
= '%d'.format(info
.line_peak
[1]);
415 tab
.querySelector('#lb_otr_cur').firstChild
.data
= '%d'.format(info
.line_current
[2]);
416 tab
.querySelector('#lb_otr_avg').firstChild
.data
= '%d'.format(info
.line_average
[2]);
417 tab
.querySelector('#lb_otr_peak').firstChild
.data
= '%d'.format(info
.line_peak
[2]);
425 handleSaveApply
: null,