02119451aeebb3e57598ea2584bb6e15b4fe2765
8 'require tools.prng as random';
11 callFrequencyList
: rpc
.declare({
15 expect
: { results
: [] }
18 callInfo
: rpc
.declare({
25 render_signal_badge: function(signalPercent
, signalValue
) {
26 var icon
, title
, value
;
28 if (signalPercent
< 0)
29 icon
= L
.resource('icons/signal-none.png');
30 else if (signalPercent
== 0)
31 icon
= L
.resource('icons/signal-0.png');
32 else if (signalPercent
< 25)
33 icon
= L
.resource('icons/signal-0-25.png');
34 else if (signalPercent
< 50)
35 icon
= L
.resource('icons/signal-25-50.png');
36 else if (signalPercent
< 75)
37 icon
= L
.resource('icons/signal-50-75.png');
39 icon
= L
.resource('icons/signal-75-100.png');
41 value
= '%d\xa0%s'.format(signalValue
, _('dBm'));
42 title
= '%s: %d %s'.format(_('Signal'), signalValue
, _('dBm'));
45 'class': 'ifacebadge',
47 'data-signal': signalValue
49 E('img', { 'src': icon
}),
54 add_wifi_to_graph: function(chan_analysis
, res
, scanCache
, channels
, channel_width
) {
55 var offset_tbl
= chan_analysis
.offset_tbl
,
56 height
= chan_analysis
.graph
.offsetHeight
- 2,
57 step
= chan_analysis
.col_width
,
58 height_diff
= (height
-(height
-(res
.signal
*-4)));
60 if (scanCache
[res
.bssid
].color
== null)
61 scanCache
[res
.bssid
].color
= random
.derive_color(res
.bssid
);
63 if (scanCache
[res
.bssid
].graph
== null)
64 scanCache
[res
.bssid
].graph
= [];
66 channels
.forEach(function(channel
) {
67 var chan_offset
= offset_tbl
[channel
],
69 (chan_offset
-(step
*channel_width
))+','+height
,
70 (chan_offset
-(step
*(channel_width
-1)))+','+height_diff
,
71 (chan_offset
+(step
*(channel_width
-1)))+','+height_diff
,
72 (chan_offset
+(step
*(channel_width
)))+','+height
75 if (scanCache
[res
.bssid
].graph
[i
] == null) {
76 var group
= document
.createElementNS('http://www.w3.org/2000/svg', 'g'),
77 line
= document
.createElementNS('http://www.w3.org/2000/svg', 'polyline'),
78 text
= document
.createElementNS('http://www.w3.org/2000/svg', 'text'),
79 color
= scanCache
[res
.bssid
].color
;
81 line
.setAttribute('style', 'fill:'+color
+'4f'+';stroke:'+color
+';stroke-width:0.5');
82 text
.setAttribute('style', 'fill:'+color
+';font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
83 text
.appendChild(document
.createTextNode(res
.ssid
|| res
.bssid
));
85 group
.appendChild(line
)
86 group
.appendChild(text
)
88 chan_analysis
.graph
.firstElementChild
.appendChild(group
);
89 scanCache
[res
.bssid
].graph
[i
] = { group
: group
, line
: line
, text
: text
};
92 scanCache
[res
.bssid
].graph
[i
].text
.setAttribute('x', chan_offset
-step
);
93 scanCache
[res
.bssid
].graph
[i
].text
.setAttribute('y', height_diff
- 2);
94 scanCache
[res
.bssid
].graph
[i
].line
.setAttribute('points', points
);
95 scanCache
[res
.bssid
].graph
[i
].group
.style
.zIndex
= res
.signal
*-1;
96 scanCache
[res
.bssid
].graph
[i
].group
.style
.opacity
= res
.stale
? '0.5' : null;
100 create_channel_graph: function(chan_analysis
, freq_tbl
, band
) {
101 var columns
= (band
!= 2) ? freq_tbl
.length
* 4 : freq_tbl
.length
+ 3,
102 chan_graph
= chan_analysis
.graph
,
103 G
= chan_graph
.firstElementChild
,
104 step
= (chan_graph
.offsetWidth
- 2) / columns
,
107 function createGraphHLine(graph
, pos
, width
, dash
) {
108 var elem
= document
.createElementNS('http://www.w3.org/2000/svg', 'line');
109 elem
.setAttribute('x1', pos
);
110 elem
.setAttribute('y1', 0);
111 elem
.setAttribute('x2', pos
);
112 elem
.setAttribute('y2', '100%');
113 elem
.setAttribute('style', 'stroke:black;stroke-width:'+width
+';stroke-dasharray:'+dash
);
114 graph
.appendChild(elem
);
117 function createGraphText(graph
, pos
, text
) {
118 var elem
= document
.createElementNS('http://www.w3.org/2000/svg', 'text');
119 elem
.setAttribute('y', 15);
120 elem
.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
121 elem
.setAttribute('x', pos
+ 5);
122 elem
.appendChild(document
.createTextNode(text
));
123 graph
.appendChild(elem
);
126 chan_analysis
.col_width
= step
;
128 createGraphHLine(G
,curr_offset
, 0.1, 1);
129 for (var i
=0; i
< freq_tbl
.length
;i
++) {
130 var channel
= freq_tbl
[i
]
131 chan_analysis
.offset_tbl
[channel
] = curr_offset
+step
;
134 createGraphHLine(G
,curr_offset
+step
, 0.1, 3);
136 createGraphText(G
,curr_offset
-(step
/2), channel
);
138 createGraphText(G
,curr_offset
-step
, channel
);
140 createGraphHLine(G
,curr_offset
+step
, 0.1, 0);
141 createGraphText(G
,curr_offset
+step
, channel
);
145 if ((band
!= 2) && freq_tbl
[i
+1]) {
146 var next_channel
= freq_tbl
[i
+1];
147 /* Check if we are transitioning to another 5Ghz band range */
148 if ((next_channel
- channel
) == 4) {
149 for (var j
=1; j
< 4; j
++) {
150 chan_analysis
.offset_tbl
[channel
+j
] = curr_offset
+step
;
152 createGraphHLine(G
,curr_offset
+step
, 0.1, 0);
154 createGraphHLine(G
,curr_offset
+step
, 0.1, 1);
158 chan_analysis
.offset_tbl
[channel
+1] = curr_offset
+step
;
159 createGraphHLine(G
,curr_offset
+step
, 0.1, 1);
162 chan_analysis
.offset_tbl
[next_channel
-2] = curr_offset
+step
;
163 createGraphHLine(G
,curr_offset
+step
, 0.5, 0);
166 chan_analysis
.offset_tbl
[next_channel
-1] = curr_offset
+step
;
167 createGraphHLine(G
,curr_offset
+step
, 0.1, 1);
172 createGraphHLine(G
,curr_offset
+step
, 0.1, 1);
174 chan_analysis
.tab
.addEventListener('cbi-tab-active', L
.bind(function(ev
) {
175 this.active_tab
= ev
.detail
.tab
;
176 if (!this.radios
[this.active_tab
].loadedOnce
)
181 handleScanRefresh: function() {
182 if (!this.active_tab
)
185 var radio
= this.radios
[this.active_tab
];
188 radio
.dev
.getScanList(),
189 this.callInfo(radio
.dev
.getName())
190 ]).then(L
.bind(function(data
) {
191 var results
= data
[0],
192 local_wifi
= data
[1],
194 chan_analysis
= radio
.graph
,
195 scanCache
= radio
.scanCache
,
200 for (var i
= 0; i
< results
.length
; i
++) {
201 if (scanCache
[results
[i
].bssid
] == null)
202 scanCache
[results
[i
].bssid
] = {};
204 scanCache
[results
[i
].bssid
].data
= results
[i
];
205 scanCache
[results
[i
].bssid
].data
.stale
= false;
208 if (band
+ 'g' == radio
.dev
.get('band')) {
209 if (scanCache
[local_wifi
.bssid
] == null)
210 scanCache
[local_wifi
.bssid
] = {};
212 scanCache
[local_wifi
.bssid
].data
= local_wifi
;
214 if (chan_analysis
.offset_tbl
[local_wifi
.channel
] != null && local_wifi
.center_chan1
) {
215 var center_channels
= [local_wifi
.center_chan1
],
216 chan_width_text
= local_wifi
.htmode
.replace(/(V)*H[TE]/,''), /* Handle HT VHT HE */
217 chan_width
= parseInt(chan_width_text
)/10;
219 if (local_wifi
.center_chan2
) {
220 center_channels
.push(local_wifi
.center_chan2
);
224 local_wifi
.signal
= -10;
225 local_wifi
.ssid
= 'Local Interface';
227 this.add_wifi_to_graph(chan_analysis
, local_wifi
, scanCache
, center_channels
, chan_width
);
229 this.render_signal_badge(q
, local_wifi
.signal
),
231 E('span', { 'style': 'color:'+scanCache
[local_wifi
.bssid
].color
}, '⬤ '),
234 '%d'.format(local_wifi
.channel
),
235 '%h MHz'.format(chan_width_text
),
236 '%h'.format(local_wifi
.mode
),
237 '%h'.format(local_wifi
.bssid
)
242 for (var k
in scanCache
)
243 if (scanCache
[k
].data
.stale
)
244 results
.push(scanCache
[k
].data
);
246 results
.sort(function(a
, b
) {
247 if (a
.channel
- b
.channel
)
252 else if (a
.ssid
> b
.ssid
)
255 if (a
.bssid
< b
.bssid
)
257 else if (a
.bssid
> b
.bssid
)
261 for (var i
= 0; i
< results
.length
; i
++) {
262 var res
= results
[i
],
263 qv
= res
.quality
|| 0,
264 qm
= res
.quality_max
|| 0,
265 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
266 s
= res
.stale
? 'opacity:0.5' : '',
267 center_channels
= [res
.channel
],
270 /* Skip WiFi not supported by the current band */
271 if (band
!= res
.band
)
273 if (chan_analysis
.offset_tbl
[res
.channel
] == null)
276 res
.channel_width
= "20 MHz";
277 if (res
.ht_operation
!= null)
278 if (res
.ht_operation
.channel_width
== 2040) { /* 40 MHz Channel Enabled */
279 if (res
.ht_operation
.secondary_channel_offset
== "below") {
280 res
.channel_width
= "40 MHz";
281 chan_width
= 4; /* 40 MHz Channel Used */
282 center_channels
[0] -= 2;
283 } else if (res
.ht_operation
.secondary_channel_offset
== "above") {
284 res
.channel_width
= "40 MHz";
285 chan_width
= 4; /* 40 MHz Channel Used */
286 center_channels
[0] += 2;
288 res
.channel_width
= "20 MHz (40 MHz Intolerant)";
292 if (res
.vht_operation
!= null) {
293 center_channels
[0] = res
.vht_operation
.center_freq_1
;
294 if (res
.vht_operation
.channel_width
== 80) {
296 res
.channel_width
= "80 MHz";
297 } else if (res
.vht_operation
.channel_width
== 8080) {
298 res
.channel_width
= "80+80 MHz";
300 center_channels
.push(res
.vht_operation
.center_freq_2
);
301 } else if (res
.vht_operation
.channel_width
== 160) {
302 res
.channel_width
= "160 MHz";
307 this.add_wifi_to_graph(chan_analysis
, res
, scanCache
, center_channels
, chan_width
);
310 E('span', { 'style': s
}, this.render_signal_badge(q
, res
.signal
)),
311 E('span', { 'style': s
}, [
312 E('span', { 'style': 'color:'+scanCache
[results
[i
].bssid
].color
}, '⬤ '),
313 (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))
315 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
316 E('span', { 'style': s
}, '%h'.format(res
.channel_width
)),
317 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
318 E('span', { 'style': s
}, '%h'.format(res
.bssid
))
321 scanCache
[results
[i
].bssid
].data
.stale
= true;
324 cbi_update_table(table
, rows
);
326 if (!radio
.loadedOnce
) {
327 radio
.loadedOnce
= true;
335 loadSVG : function(src
) {
336 return request
.get(src
).then(function(response
) {
338 throw new Error(response
.statusText
);
341 'id': 'channel_graph',
342 'style': 'width:100%;height:400px;border:1px solid #000;background:#fff'
343 }, E(response
.text()));
349 this.loadSVG(L
.resource('svg/channel_analysis.svg')),
350 network
.getWifiDevices().then(L
.bind(function(data
) {
351 var tasks
= [], ret
= [];
353 for (var i
= 0; i
< data
.length
; i
++) {
354 ret
[data
[i
].getName()] = { dev
: data
[i
] };
356 tasks
.push(this.callFrequencyList(data
[i
].getName())
357 .then(L
.bind(function(radio
, data
) {
358 ret
[radio
.getName()].freq
= data
;
362 return Promise
.all(tasks
).then(function() { return ret
; })
367 render: function(data
) {
371 var h2
= E('div', {'class' : 'cbi-title-section'}, [
372 E('h2', {'class': 'cbi-title-field'}, [ _('Channel Analysis') ]),
373 E('div', {'class': 'cbi-title-buttons' }, [
375 'class': 'cbi-button cbi-button-edit',
376 'click': ui
.createHandlerFn(this, 'handleScanRefresh')
377 }, [ _('Refresh Channels') ])])
380 var tabs
= E('div', {}, E('div'));
382 for (var ifname
in wifiDevs
) {
384 [2] : { title
: '2.4GHz', channels
: [] },
385 [5] : { title
: '5GHz', channels
: [] },
388 /* Split FrequencyList in Bands */
389 wifiDevs
[ifname
].freq
.forEach(function(freq
) {
390 if (bands
[freq
.band
])
391 bands
[freq
.band
].channels
.push(freq
.channel
);
394 for (var band
in bands
) {
395 if (bands
[band
].channels
.length
== 0)
398 var csvg
= svg
.cloneNode(true),
399 table
= E('table', { 'class': 'table' }, [
400 E('tr', { 'class': 'tr table-titles' }, [
401 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
402 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
403 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
404 E('th', { 'class': 'th col-3 middle left' }, _('Channel Width')),
405 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
406 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID'))
409 tab
= E('div', { 'data-tab': ifname
+band
, 'data-tab-title': ifname
+' ('+bands
[band
].title
+')' },
410 [E('br'),csvg
,E('br'),table
,E('br')]),
418 this.radios
[ifname
+band
] = {
419 dev
: wifiDevs
[ifname
].dev
,
427 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
429 tabs
.firstElementChild
.appendChild(tab
)
431 requestAnimationFrame(L
.bind(this.create_channel_graph
, this, graph_data
, bands
[band
].channels
, band
));
435 ui
.tabs
.initTabGroup(tabs
.firstElementChild
.childNodes
);
437 this.pollFn
= L
.bind(this.handleScanRefresh
, this);
438 poll
.add(this.pollFn
);
440 return E('div', {}, [h2
, tabs
]);
443 handleSaveApply
: null,