treewide: import utility classes explicitly
[project/luci.git] / modules / luci-mod-status / htdocs / luci-static / resources / view / status / include / 60_wifi.js
1 'use strict';
2 'require baseclass';
3 'require dom';
4 'require network';
5
6 function renderbox(radio, networks) {
7 var chan = null,
8 freq = null,
9 rate = null,
10 badges = [];
11
12 for (var i = 0; i < networks.length; i++) {
13 var net = networks[i],
14 is_assoc = (net.getBSSID() != '00:00:00:00:00:00' && net.getChannel() && !net.isDisabled()),
15 quality = net.getSignalPercent();
16
17 var icon;
18 if (net.isDisabled())
19 icon = L.resource('icons/signal-none.png');
20 else if (quality <= 0)
21 icon = L.resource('icons/signal-0.png');
22 else if (quality < 25)
23 icon = L.resource('icons/signal-0-25.png');
24 else if (quality < 50)
25 icon = L.resource('icons/signal-25-50.png');
26 else if (quality < 75)
27 icon = L.resource('icons/signal-50-75.png');
28 else
29 icon = L.resource('icons/signal-75-100.png');
30
31 var badge = renderBadge(
32 icon,
33 '%s: %d dBm / %s: %d%%'.format(_('Signal'), net.getSignal(), _('Quality'), quality),
34 _('SSID'), net.getActiveSSID() || '?',
35 _('Mode'), net.getActiveMode(),
36 _('BSSID'), is_assoc ? (net.getActiveBSSID() || '-') : null,
37 _('Encryption'), is_assoc ? net.getActiveEncryption() : null,
38 _('Associations'), is_assoc ? (net.assoclist.length || '-') : null,
39 null, is_assoc ? null : E('em', net.isDisabled() ? _('Wireless is disabled') : _('Wireless is not associated')));
40
41 badges.push(badge);
42
43 chan = (chan != null) ? chan : net.getChannel();
44 freq = (freq != null) ? freq : net.getFrequency();
45 rate = (rate != null) ? rate : net.getBitRate();
46 }
47
48 return E('div', { class: 'ifacebox' }, [
49 E('div', { class: 'ifacebox-head center ' + (radio.isUp() ? 'active' : '') },
50 E('strong', radio.getName())),
51 E('div', { class: 'ifacebox-body left' }, [
52 L.itemlist(E('span'), [
53 _('Type'), radio.getI18n().replace(/^Generic | Wireless Controller .+$/g, ''),
54 _('Channel'), chan ? '%d (%.3f %s)'.format(chan, freq, _('GHz')) : '-',
55 _('Bitrate'), rate ? '%d %s'.format(rate, _('Mbit/s')) : '-'
56 ]),
57 E('div', {}, badges)
58 ])
59 ]);
60 }
61
62 function wifirate(rt) {
63 var s = '%.1f\xa0%s, %d\xa0%s'.format(rt.rate / 1000, _('Mbit/s'), rt.mhz, _('MHz')),
64 ht = rt.ht, vht = rt.vht,
65 mhz = rt.mhz, nss = rt.nss,
66 mcs = rt.mcs, sgi = rt.short_gi;
67
68 if (ht || vht) {
69 if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
70 if (nss) s += ', VHT-NSS\xa0%d'.format(nss);
71 if (ht) s += ', MCS\xa0%s'.format(mcs);
72 if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0');
73 }
74
75 return s;
76 }
77
78 return baseclass.extend({
79 title: _('Wireless'),
80
81 handleDelClient: function(wifinet, mac, ev) {
82 dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
83 ev.currentTarget.classList.add('spinning');
84 ev.currentTarget.disabled = true;
85 ev.currentTarget.blur();
86
87 wifinet.disconnectClient(mac, true, 5, 60000);
88 },
89
90 load: function() {
91 return Promise.all([
92 network.getWifiDevices(),
93 network.getWifiNetworks(),
94 network.getHostHints()
95 ]).then(function(radios_networks_hints) {
96 var tasks = [];
97
98 for (var i = 0; i < radios_networks_hints[1].length; i++)
99 tasks.push(L.resolveDefault(radios_networks_hints[1][i].getAssocList(), []).then(L.bind(function(net, list) {
100 net.assoclist = list.sort(function(a, b) { return a.mac > b.mac });
101 }, this, radios_networks_hints[1][i])));
102
103 return Promise.all(tasks).then(function() {
104 return radios_networks_hints;
105 });
106 });
107 },
108
109 render: function(data) {
110 var seen = {},
111 radios = data[0],
112 networks = data[1],
113 hosthints = data[2];
114
115 var table = E('div', { 'class': 'network-status-table' });
116
117 for (var i = 0; i < radios.sort(function(a, b) { a.getName() > b.getName() }).length; i++)
118 table.appendChild(renderbox(radios[i],
119 networks.filter(function(net) { return net.getWifiDeviceName() == radios[i].getName() })));
120
121 if (!table.lastElementChild)
122 return null;
123
124 var assoclist = E('div', { 'class': 'table assoclist' }, [
125 E('div', { 'class': 'tr table-titles' }, [
126 E('div', { 'class': 'th nowrap' }, _('Network')),
127 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
128 E('div', { 'class': 'th' }, _('Host')),
129 E('div', { 'class': 'th' }, '%s / %s'.format(_('Signal'), _('Noise'))),
130 E('div', { 'class': 'th' }, '%s / %s'.format(_('RX Rate'), _('TX Rate')))
131 ])
132 ]);
133
134 var rows = [];
135
136 for (var i = 0; i < networks.length; i++) {
137 for (var k = 0; k < networks[i].assoclist.length; k++) {
138 var bss = networks[i].assoclist[k],
139 name = hosthints.getHostnameByMACAddr(bss.mac),
140 ipv4 = hosthints.getIPAddrByMACAddr(bss.mac),
141 ipv6 = hosthints.getIP6AddrByMACAddr(bss.mac);
142
143 var icon;
144 var q = Math.min((bss.signal + 110) / 70 * 100, 100);
145 if (q == 0)
146 icon = L.resource('icons/signal-0.png');
147 else if (q < 25)
148 icon = L.resource('icons/signal-0-25.png');
149 else if (q < 50)
150 icon = L.resource('icons/signal-25-50.png');
151 else if (q < 75)
152 icon = L.resource('icons/signal-50-75.png');
153 else
154 icon = L.resource('icons/signal-75-100.png');
155
156 var sig_title, sig_value;
157
158 if (bss.noise) {
159 sig_value = '%d/%d\xa0%s'.format(bss.signal, bss.noise, _('dBm'));
160 sig_title = '%s: %d %s / %s: %d %s / %s %d'.format(
161 _('Signal'), bss.signal, _('dBm'),
162 _('Noise'), bss.noise, _('dBm'),
163 _('SNR'), bss.signal - bss.noise);
164 }
165 else {
166 sig_value = '%d\xa0%s'.format(bss.signal, _('dBm'));
167 sig_title = '%s: %d %s'.format(_('Signal'), bss.signal, _('dBm'));
168 }
169
170 var hint;
171
172 if (name && ipv4 && ipv6)
173 hint = '%s <span class="hide-xs">(%s, %s)</span>'.format(name, ipv4, ipv6);
174 else if (name && (ipv4 || ipv6))
175 hint = '%s <span class="hide-xs">(%s)</span>'.format(name, ipv4 || ipv6);
176 else
177 hint = name || ipv4 || ipv6 || '?';
178
179 var row = [
180 E('span', {
181 'class': 'ifacebadge',
182 'title': networks[i].getI18n(),
183 'data-ifname': networks[i].getIfname(),
184 'data-ssid': networks[i].getActiveSSID()
185 }, [
186 E('img', { 'src': L.resource('icons/wifi.png') }),
187 E('span', {}, [
188 ' ', networks[i].getShortName(),
189 E('small', {}, [ ' (', networks[i].getIfname(), ')' ])
190 ])
191 ]),
192 bss.mac,
193 hint,
194 E('span', {
195 'class': 'ifacebadge',
196 'title': sig_title,
197 'data-signal': bss.signal,
198 'data-noise': bss.noise
199 }, [
200 E('img', { 'src': icon }),
201 E('span', {}, [
202 ' ', sig_value
203 ])
204 ]),
205 E('span', {}, [
206 E('span', wifirate(bss.rx)),
207 E('br'),
208 E('span', wifirate(bss.tx))
209 ])
210 ];
211
212 if (networks[i].isClientDisconnectSupported()) {
213 if (assoclist.firstElementChild.childNodes.length < 6)
214 assoclist.firstElementChild.appendChild(E('div', { 'class': 'th cbi-section-actions' }));
215
216 row.push(E('button', {
217 'class': 'cbi-button cbi-button-remove',
218 'click': L.bind(this.handleDelClient, this, networks[i], bss.mac)
219 }, [ _('Disconnect') ]));
220 }
221 else {
222 row.push('-');
223 }
224
225 rows.push(row);
226 }
227 }
228
229 cbi_update_table(assoclist, rows, E('em', _('No information available')));
230
231 return E([
232 table,
233 E('h3', _('Associated Stations')),
234 assoclist
235 ]);
236 }
237 });