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
, freq
) {
101 var is5GHz
= freq
== '5GHz',
102 columns
= is5GHz
? freq_tbl
.length
* 4 : freq_tbl
.length
+ 3,
103 chan_graph
= chan_analysis
.graph
,
104 G
= chan_graph
.firstElementChild
,
105 step
= (chan_graph
.offsetWidth
- 2) / columns
,
108 function createGraphHLine(graph
, pos
) {
109 var elem
= document
.createElementNS('http://www.w3.org/2000/svg', 'line');
110 elem
.setAttribute('x1', pos
);
111 elem
.setAttribute('y1', 0);
112 elem
.setAttribute('x2', pos
);
113 elem
.setAttribute('y2', '100%');
114 elem
.setAttribute('style', 'stroke:black;stroke-width:0.1');
115 graph
.appendChild(elem
);
118 function createGraphText(graph
, pos
, text
) {
119 var elem
= document
.createElementNS('http://www.w3.org/2000/svg', 'text');
120 elem
.setAttribute('y', 15);
121 elem
.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
122 elem
.setAttribute('x', pos
+ 5);
123 elem
.appendChild(document
.createTextNode(text
));
124 graph
.appendChild(elem
);
127 chan_analysis
.col_width
= step
;
129 createGraphHLine(G
,curr_offset
);
130 for (var i
=0; i
< freq_tbl
.length
;i
++) {
131 var channel
= freq_tbl
[i
]
132 chan_analysis
.offset_tbl
[channel
] = curr_offset
+step
;
134 createGraphHLine(G
,curr_offset
+step
);
135 createGraphText(G
,curr_offset
+step
, channel
);
138 if (is5GHz
&& freq_tbl
[i
+1]) {
139 var next_channel
= freq_tbl
[i
+1];
140 /* Check if we are transitioning to another 5Ghz band range */
141 if ((next_channel
- channel
) == 4) {
142 for (var j
=1; j
< 4; j
++) {
143 chan_analysis
.offset_tbl
[channel
+j
] = curr_offset
+step
;
144 createGraphHLine(G
,curr_offset
+step
);
148 chan_analysis
.offset_tbl
[channel
+1] = curr_offset
+step
;
149 createGraphHLine(G
,curr_offset
+step
);
152 chan_analysis
.offset_tbl
[next_channel
-2] = curr_offset
+step
;
153 createGraphHLine(G
,curr_offset
+step
);
156 chan_analysis
.offset_tbl
[next_channel
-1] = curr_offset
+step
;
157 createGraphHLine(G
,curr_offset
+step
);
162 createGraphHLine(G
,curr_offset
+step
);
164 chan_analysis
.tab
.addEventListener('cbi-tab-active', L
.bind(function(ev
) {
165 this.active_tab
= ev
.detail
.tab
;
169 handleScanRefresh: function() {
170 if (!this.active_tab
)
173 var radioDev
= this.radios
[this.active_tab
].dev
,
174 table
= this.radios
[this.active_tab
].table
,
175 chan_analysis
= this.radios
[this.active_tab
].graph
,
176 scanCache
= this.radios
[this.active_tab
].scanCache
;
179 radioDev
.getScanList(),
180 this.callInfo(radioDev
.getName())
181 ]).then(L
.bind(function(data
) {
182 var results
= data
[0],
183 local_wifi
= data
[1];
187 for (var i
= 0; i
< results
.length
; i
++) {
188 if (scanCache
[results
[i
].bssid
] == null)
189 scanCache
[results
[i
].bssid
] = {};
191 scanCache
[results
[i
].bssid
].data
= results
[i
];
194 if (scanCache
[local_wifi
.bssid
] == null)
195 scanCache
[local_wifi
.bssid
] = {};
197 scanCache
[local_wifi
.bssid
].data
= local_wifi
;
199 if (chan_analysis
.offset_tbl
[local_wifi
.channel
] != null && local_wifi
.center_chan1
) {
200 var center_channels
= [local_wifi
.center_chan1
],
201 chan_width_text
= local_wifi
.htmode
.replace(/(V)*HT/,''),
202 chan_width
= parseInt(chan_width_text
)/10;
204 if (local_wifi
.center_chan2
) {
205 center_channels
.push(local_wifi
.center_chan2
);
209 local_wifi
.signal
= -10;
210 local_wifi
.ssid
= 'Local Interface';
212 this.add_wifi_to_graph(chan_analysis
, local_wifi
, scanCache
, center_channels
, chan_width
);
214 this.render_signal_badge(q
, local_wifi
.signal
),
216 E('span', { 'style': 'color:'+scanCache
[local_wifi
.bssid
].color
}, '⬤ '),
219 '%d'.format(local_wifi
.channel
),
220 '%h MHz'.format(chan_width_text
),
221 '%h'.format(local_wifi
.mode
),
222 '%h'.format(local_wifi
.bssid
)
226 for (var k
in scanCache
)
227 if (scanCache
[k
].stale
)
228 results
.push(scanCache
[k
].data
);
230 results
.sort(function(a
, b
) {
231 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
238 else if (a
.ssid
> b
.ssid
)
241 if (a
.bssid
< b
.bssid
)
243 else if (a
.bssid
> b
.bssid
)
247 for (var i
= 0; i
< results
.length
; i
++) {
248 var res
= results
[i
],
249 qv
= res
.quality
|| 0,
250 qm
= res
.quality_max
|| 0,
251 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
252 s
= res
.stale
? 'opacity:0.5' : '',
253 center_channels
= [res
.channel
],
256 /* Skip WiFi not supported by the current band */
257 if (chan_analysis
.offset_tbl
[res
.channel
] == null)
260 res
.channel_width
= "20 MHz";
261 if (res
.ht_operation
!= null)
262 if (res
.ht_operation
.channel_width
== 2040) { /* 40 MHz Channel Enabled */
263 if (res
.ht_operation
.secondary_channel_offset
== "below") {
264 res
.channel_width
= "40 MHz";
265 chan_width
= 4; /* 40 MHz Channel Used */
266 center_channels
[0] -= 2;
267 } else if (res
.ht_operation
.secondary_channel_offset
== "above") {
268 res
.channel_width
= "40 MHz";
269 chan_width
= 4; /* 40 MHz Channel Used */
270 center_channels
[0] += 2;
272 res
.channel_width
= "20 MHz (40 MHz Intolerant)";
276 if (res
.vht_operation
!= null) {
277 center_channels
[0] = res
.vht_operation
.center_freq_1
;
278 if (res
.vht_operation
.channel_width
== 80) {
280 res
.channel_width
= "80 MHz";
281 } else if (res
.vht_operation
.channel_width
== 8080) {
282 res
.channel_width
= "80+80 MHz";
284 center_channels
.push(res
.vht_operation
.center_freq_2
);
285 } else if (res
.vht_operation
.channel_width
== 160) {
286 res
.channel_width
= "160 MHz";
291 this.add_wifi_to_graph(chan_analysis
, res
, scanCache
, center_channels
, chan_width
);
294 E('span', { 'style': s
}, this.render_signal_badge(q
, res
.signal
)),
295 E('span', { 'style': s
}, [
296 E('span', { 'style': 'color:'+scanCache
[results
[i
].bssid
].color
}, '⬤ '),
297 (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))
299 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
300 E('span', { 'style': s
}, '%h'.format(res
.channel_width
)),
301 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
302 E('span', { 'style': s
}, '%h'.format(res
.bssid
))
308 cbi_update_table(table
, rows
);
314 loadSVG : function(src
) {
315 return request
.get(src
).then(function(response
) {
317 throw new Error(response
.statusText
);
320 'id': 'channel_graph',
321 'style': 'width:100%;height:400px;border:1px solid #000;background:#fff'
322 }, E(response
.text()));
328 this.loadSVG(L
.resource('svg/channel_analysis.svg')),
329 network
.getWifiDevices().then(L
.bind(function(data
) {
330 var tasks
= [], ret
= [];
332 for (var i
= 0; i
< data
.length
; i
++) {
333 ret
[data
[i
].getName()] = { dev
: data
[i
] };
335 tasks
.push(this.callFrequencyList(data
[i
].getName())
336 .then(L
.bind(function(radio
, data
) {
337 ret
[radio
.getName()].freq
= data
;
341 return Promise
.all(tasks
).then(function() { return ret
; })
346 render: function(data
) {
350 var v
= E('div', {}, E('div'));
352 for (var ifname
in wifiDevs
) {
358 /* Split FrequencyList in Bands */
359 wifiDevs
[ifname
].freq
.forEach(function(freq
) {
360 if (freq
.mhz
>= 5000) {
361 freq_tbl
['5GHz'].push(freq
.channel
);
363 freq_tbl
['2.4GHz'].push(freq
.channel
);
367 for (var freq
in freq_tbl
) {
368 if (freq_tbl
[freq
].length
== 0)
371 var csvg
= svg
.cloneNode(true),
372 table
= E('div', { 'class': 'table' }, [
373 E('div', { 'class': 'tr table-titles' }, [
374 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
375 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
376 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
377 E('div', { 'class': 'th col-3 middle left' }, _('Channel Width')),
378 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
379 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID'))
382 tab
= E('div', { 'data-tab': ifname
+freq
, 'data-tab-title': ifname
+' ('+freq
+')' },
383 [E('br'),csvg
,E('br'),table
,E('br')]),
391 this.radios
[ifname
+freq
] = {
392 dev
: wifiDevs
[ifname
].dev
,
398 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
400 v
.firstElementChild
.appendChild(tab
)
402 requestAnimationFrame(L
.bind(this.create_channel_graph
, this, graph_data
, freq_tbl
[freq
], freq
));
406 ui
.tabs
.initTabGroup(v
.firstElementChild
.childNodes
);
408 this.pollFn
= L
.bind(this.handleScanRefresh
, this);
410 poll
.add(this.pollFn
);
416 handleSaveApply
: null,