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