9 return baseclass
.extend({
13 Disabled
: _('Disabled'),
15 'Timed-out': _('Timed-out'),
16 Overlap
: _('Overlap'),
20 callSessionAccess
: rpc
.declare({
23 params
: [ 'scope', 'object', 'function' ],
24 expect
: { 'access': false }
27 wifirate: function(rt
) {
28 var s
= '%.1f\xa0%s, %d\xa0%s'.format(rt
.rate
/ 1000, _('Mbit/s'), rt
.mhz
, _('MHz')),
29 ht
= rt
.ht
, vht
= rt
.vht
,
30 mhz
= rt
.mhz
, nss
= rt
.nss
,
31 mcs
= rt
.mcs
, sgi
= rt
.short_gi
,
32 he
= rt
.he
, he_gi
= rt
.he_gi
,
36 if (vht
) s
+= ', VHT-MCS\xa0%d'.format(mcs
);
37 if (nss
) s
+= ', VHT-NSS\xa0%d'.format(nss
);
38 if (ht
) s
+= ', MCS\xa0%s'.format(mcs
);
39 if (sgi
) s
+= ', ' + _('Short GI').replace(/ /g
, '\xa0');
43 s
+= ', HE-MCS\xa0%d'.format(mcs
);
44 if (nss
) s
+= ', HE-NSS\xa0%d'.format(nss
);
45 if (he_gi
) s
+= ', HE-GI\xa0%d'.format(he_gi
);
46 if (he_dcm
) s
+= ', HE-DCM\xa0%d'.format(he_dcm
);
52 handleDelClient: function(wifinet
, mac
, ev
, cmd
) {
53 var exec
= cmd
|| 'disconnect';
55 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
56 ev
.currentTarget
.classList
.add('spinning');
57 ev
.currentTarget
.disabled
= true;
58 ev
.currentTarget
.blur();
60 /* Disconnect client before adding to maclist */
61 wifinet
.disconnectClient(mac
, true, 5, 60000);
63 if (exec
== 'addlist') {
64 wifinet
.maclist
.push(mac
);
66 uci
.set('wireless', wifinet
.sid
, 'maclist', wifinet
.maclist
);
69 .then(L
.bind(L
.ui
.changes
.init
, L
.ui
.changes
))
70 .then(L
.bind(L
.ui
.changes
.displayChanges
, L
.ui
.changes
));
74 handleGetWPSStatus: function(wifinet
) {
76 object
: 'hostapd.%s'.format(wifinet
),
81 handleCallWPS: function(wifinet
, ev
) {
82 ev
.currentTarget
.classList
.add('spinning');
83 ev
.currentTarget
.disabled
= true;
84 ev
.currentTarget
.blur();
87 object
: 'hostapd.%s'.format(wifinet
),
92 handleCancelWPS: function(wifinet
, ev
) {
93 ev
.currentTarget
.classList
.add('spinning');
94 ev
.currentTarget
.disabled
= true;
95 ev
.currentTarget
.blur();
98 object
: 'hostapd.%s'.format(wifinet
),
103 renderbox: function(radio
, networks
) {
109 for (var i
= 0; i
< networks
.length
; i
++) {
110 var net
= networks
[i
],
111 is_assoc
= (net
.getBSSID() != '00:00:00:00:00:00' && net
.getChannel() && !net
.isDisabled()),
112 quality
= net
.getSignalPercent();
115 if (net
.isDisabled())
116 icon
= L
.resource('icons/signal-none.png');
117 else if (quality
<= 0)
118 icon
= L
.resource('icons/signal-0.png');
119 else if (quality
< 25)
120 icon
= L
.resource('icons/signal-0-25.png');
121 else if (quality
< 50)
122 icon
= L
.resource('icons/signal-25-50.png');
123 else if (quality
< 75)
124 icon
= L
.resource('icons/signal-50-75.png');
126 icon
= L
.resource('icons/signal-75-100.png');
128 var WPS_button
= null;
130 if (net
.isWPSEnabled
) {
131 if (net
.wps_status
== 'Active') {
132 WPS_button
= E('button', {
133 'class' : 'cbi-button cbi-button-remove',
134 'click': L
.bind(this.handleCancelWPS
, this, net
.getIfname()),
135 }, [ _('Stop WPS') ])
137 WPS_button
= E('button', {
138 'class' : 'cbi-button cbi-button-apply',
139 'click': L
.bind(this.handleCallWPS
, this, net
.getIfname()),
140 }, [ _('Start WPS') ])
144 var badge
= renderBadge(
146 '%s: %d dBm / %s: %d%%'.format(_('Signal'), net
.getSignal(), _('Quality'), quality
),
147 _('SSID'), net
.getActiveSSID() || '?',
148 _('Mode'), net
.getActiveMode(),
149 _('BSSID'), is_assoc
? (net
.getActiveBSSID() || '-') : null,
150 _('Encryption'), is_assoc
? net
.getActiveEncryption() : null,
151 _('Associations'), is_assoc
? (net
.assoclist
.length
|| '-') : null,
152 null, is_assoc
? null : E('em', net
.isDisabled() ? _('Wireless is disabled') : _('Wireless is not associated')),
153 _('WPS status'), this.WPSTranslateTbl
[net
.wps_status
],
159 chan
= (chan
!= null) ? chan
: net
.getChannel();
160 freq
= (freq
!= null) ? freq
: net
.getFrequency();
161 rate
= (rate
!= null) ? rate
: net
.getBitRate();
164 return E('div', { class: 'ifacebox' }, [
165 E('div', { class: 'ifacebox-head center ' + (radio
.isUp() ? 'active' : '') },
166 E('strong', radio
.getName())),
167 E('div', { class: 'ifacebox-body left' }, [
168 L
.itemlist(E('span'), [
169 _('Type'), radio
.getI18n().replace(/^Generic | Wireless Controller .+$/g, ''),
170 _('Channel'), chan
? '%d (%.3f %s)'.format(chan
, freq
, _('GHz')) : '-',
171 _('Bitrate'), rate
? '%d %s'.format(rate
, _('Mbit/s')) : '-',
182 network
.getWifiDevices(),
183 network
.getWifiNetworks(),
184 network
.getHostHints(),
185 this.callSessionAccess('access-group', 'luci-mod-status-index-wifi', 'read'),
186 this.callSessionAccess('access-group', 'luci-mod-status-index-wifi', 'write'),
188 ]).then(L
.bind(function(data
) {
190 radios_networks_hints
= data
[1],
191 hasWPS
= L
.hasSystemFeature('hostapd', 'wps');
193 for (var i
= 0; i
< radios_networks_hints
.length
; i
++) {
194 tasks
.push(L
.resolveDefault(radios_networks_hints
[i
].getAssocList(), []).then(L
.bind(function(net
, list
) {
195 net
.assoclist
= list
.sort(function(a
, b
) { return a
.mac
> b
.mac
});
196 }, this, radios_networks_hints
[i
])));
198 if (hasWPS
&& uci
.get('wireless', radios_networks_hints
[i
].sid
, 'wps_pushbutton') == '1') {
199 radios_networks_hints
[i
].isWPSEnabled
= true;
200 tasks
.push(L
.resolveDefault(this.handleGetWPSStatus(radios_networks_hints
[i
].getIfname()), null)
201 .then(L
.bind(function(net
, data
) {
202 net
.wps_status
= data
? data
.pbc_status
: _('No Data');
203 }, this, radios_networks_hints
[i
])));
207 return Promise
.all(tasks
).then(function() {
213 render: function(data
) {
218 hasReadPermission
= data
[3],
219 hasWritePermission
= data
[4];
221 var table
= E('div', { 'class': 'network-status-table' });
223 for (var i
= 0; i
< radios
.sort(function(a
, b
) { a
.getName() > b
.getName() }).length
; i
++)
224 table
.appendChild(this.renderbox(radios
[i
],
225 networks
.filter(function(net
) { return net
.getWifiDeviceName() == radios
[i
].getName() })));
227 if (!table
.lastElementChild
)
230 var assoclist
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
231 E('tr', { 'class': 'tr table-titles' }, [
232 E('th', { 'class': 'th nowrap' }, _('Network')),
233 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
234 E('th', { 'class': 'th' }, _('Host')),
235 E('th', { 'class': 'th' }, '%s / %s'.format(_('Signal'), _('Noise'))),
236 E('th', { 'class': 'th' }, '%s / %s'.format(_('RX Rate'), _('TX Rate')))
242 for (var i
= 0; i
< networks
.length
; i
++) {
243 var macfilter
= uci
.get('wireless', networks
[i
].sid
, 'macfilter'),
246 if (macfilter
!= null && macfilter
!= 'disable') {
247 networks
[i
].maclist
= L
.toArray(uci
.get('wireless', networks
[i
].sid
, 'maclist'));
248 for (var j
= 0; j
< networks
[i
].maclist
.length
; j
++) {
249 var mac
= networks
[i
].maclist
[j
].toUpperCase();
254 for (var k
= 0; k
< networks
[i
].assoclist
.length
; k
++) {
255 var bss
= networks
[i
].assoclist
[k
],
256 name
= hosthints
.getHostnameByMACAddr(bss
.mac
),
257 ipv4
= hosthints
.getIPAddrByMACAddr(bss
.mac
),
258 ipv6
= hosthints
.getIP6AddrByMACAddr(bss
.mac
);
261 var q
= Math
.min((bss
.signal
+ 110) / 70 * 100, 100);
263 icon
= L
.resource('icons/signal-0.png');
265 icon
= L
.resource('icons/signal-0-25.png');
267 icon
= L
.resource('icons/signal-25-50.png');
269 icon
= L
.resource('icons/signal-50-75.png');
271 icon
= L
.resource('icons/signal-75-100.png');
273 var sig_title
, sig_value
;
276 sig_value
= '%d/%d\xa0%s'.format(bss
.signal
, bss
.noise
, _('dBm'));
277 sig_title
= '%s: %d %s / %s: %d %s / %s %d'.format(
278 _('Signal'), bss
.signal
, _('dBm'),
279 _('Noise'), bss
.noise
, _('dBm'),
280 _('SNR'), bss
.signal
- bss
.noise
);
283 sig_value
= '%d\xa0%s'.format(bss
.signal
, _('dBm'));
284 sig_title
= '%s: %d %s'.format(_('Signal'), bss
.signal
, _('dBm'));
289 if (name
&& ipv4
&& ipv6
)
290 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
291 else if (name
&& (ipv4
|| ipv6
))
292 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
294 hint
= name
|| ipv4
|| ipv6
|| '?';
298 'class': 'ifacebadge',
299 'title': networks
[i
].getI18n(),
300 'data-ifname': networks
[i
].getIfname(),
301 'data-ssid': networks
[i
].getActiveSSID()
303 E('img', { 'src': L
.resource('icons/wifi.png') }),
305 ' ', networks
[i
].getShortName(),
306 E('small', {}, [ ' (', networks
[i
].getIfname(), ')' ])
312 'class': 'ifacebadge',
314 'data-signal': bss
.signal
,
315 'data-noise': bss
.noise
317 E('img', { 'src': icon
}),
323 E('span', this.wifirate(bss
.rx
)),
325 E('span', this.wifirate(bss
.tx
))
329 if (networks
[i
].isClientDisconnectSupported() && hasWritePermission
) {
330 if (assoclist
.firstElementChild
.childNodes
.length
< 6)
331 assoclist
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions' }));
333 if (macfilter
!= null && macfilter
!= 'disable' && !maclist
[bss
.mac
]) {
334 row
.push(new L
.ui
.ComboButton('button', {
335 'addlist': macfilter
== 'allow' ? _('Add to Whitelist') : _('Add to Blacklist'),
336 'disconnect': _('Disconnect')
338 'click': L
.bind(this.handleDelClient
, this, networks
[i
], bss
.mac
),
339 'sort': [ 'disconnect', 'addlist' ],
341 'addlist': 'btn cbi-button cbi-button-remove',
342 'disconnect': 'btn cbi-button cbi-button-remove'
348 row
.push(E('button', {
349 'class': 'cbi-button cbi-button-remove',
350 'click': L
.bind(this.handleDelClient
, this, networks
[i
], bss
.mac
)
351 }, [ _('Disconnect') ]));
362 cbi_update_table(assoclist
, rows
, E('em', _('No information available')));
366 hasReadPermission
? E('h3', _('Associated Stations')) : E([]),
367 hasReadPermission
? assoclist
: E([])