luci-app-usteer: Allow editing of settings if usteer is not running
[project/luci.git] / applications / luci-app-usteer / htdocs / luci-static / resources / view / usteer / usteer.js
1 'use strict';
2 'require view';
3 'require rpc';
4 'require poll';
5 'require dom';
6 'require ui';
7 'require form';
8 'require uci';
9 'require network';
10 'require tools.widgets as widgets';
11
12 var Hosts, Remotehosts, Remoteinfo, Localinfo, Clients, WifiNetworks;
13
14 var dns_cache = [];
15
16 function SplitWlan(wlan) {
17 var wlansplit = [];
18 if (typeof wlan.split('#')[1] !== 'undefined') {
19 wlansplit=wlan.split('#');
20 if (typeof dns_cache[wlansplit[0]] !== 'undefined') {
21 wlansplit[0]=dns_cache[wlansplit[0]];
22 }
23 } else {
24 wlansplit[0]=_('This AP');
25 wlansplit[1]=wlan;
26 }
27 return wlansplit;
28 }
29
30
31 function collectHearingClient(client_table_entries, mac) {
32 if (typeof Clients[mac] !== 'undefined') {
33 for (var wlanc in Clients[mac]) {
34 var SSID = '';
35 var freq = 0;
36 if (typeof Localinfo[wlanc] !== 'undefined') {
37 SSID = Localinfo[wlanc]['ssid'];
38 freq = Localinfo[wlanc]['freq'];
39 }
40 if (typeof Remoteinfo[wlanc] !== 'undefined') {
41 SSID = Remoteinfo[wlanc]['ssid'];
42 freq = Remoteinfo[wlanc]['freq'];
43 }
44 var wlansplit=SplitWlan(wlanc);
45 client_table_entries.push([
46 '<nobr>' + '%h'.format(wlansplit[0]) + '</nobr>',
47 '<nobr>' + '%h'.format(wlansplit[1]) + '</nobr>',
48 '%h'.format(SSID),
49 '%h'.format(freq),
50 Clients[mac][wlanc]['connected'] === true ? 'Yes' : 'No',
51 typeof Clients[mac][wlanc]['signal'] !== 'undefined' ? '%h'.format(Clients[mac][wlanc]['signal']) : ''
52 ]);
53 }
54 }
55 }
56
57 var HearingMap = form.DummyValue.extend({
58 renderWidget: function () {
59 var body = E([
60 E('h3', _('Hearing map')),
61 E('div', _('Refresh page to get new mac addresses to show up'))
62 ]);
63 for (var mac in Clients) {
64 var maciphost = '';
65 maciphost = '%h'.format(mac);
66 var macUp = mac.toUpperCase();
67 var macn = macUp.replace(/:/g,'');
68 if (typeof Hosts[macUp] !== 'undefined') {
69 if ((String(Hosts[macUp]['ipaddrs'][0]).length > 0) && (typeof Hosts[macUp]['ipaddrs'][0] !== 'undefined'))
70 maciphost += '\u2003' + Hosts[macUp]['ipaddrs'];
71 if ((String(Hosts[macUp]['name']).length > 0) && (typeof Hosts[macUp]['name'] !== 'undefined'))
72 maciphost += '\u2003%h'.format(Hosts[macUp]['name']);
73 }
74 body.appendChild(
75 E('h4', maciphost)
76 );
77 var client_table = E('table', {'class': 'table cbi-section-table','id':'client_table'+macn}, [
78 E('tr', {'class': 'tr table-titles'}, [
79 E('th', {'class': 'th'}, _('AP','Name or IP address of access point')),
80 E('th', {'class': 'th'}, _('Interface name','interface name in usteer overview')),
81 E('th', {'class': 'th', 'style': 'width:25%'}, _('SSID')),
82 E('th', {'class': 'th', 'style': 'width:15%'}, _('Frequency','BSS operating frequency in usteer overview')),
83 E('th', {'class': 'th', 'style': 'width:15%'}, _('Connected','Connection state in usteer overview')),
84 E('th', {'class': 'th', 'style': 'width:15%'}, _('Signal','Signal strength reported by wireless station in usteer overview'))
85 ])
86 ]);
87 var client_table_entries = [];
88 collectHearingClient(client_table_entries, mac);
89 cbi_update_table(client_table, client_table_entries, E('em', _('No data')));
90 body.appendChild(client_table);
91 }
92 return E('div', {'class': 'cbi-section cbi-tblsection'}, [body]);
93 }
94 });
95
96
97
98
99 function collectWlanAPInfoEntries(connectioninfo_table_entries, wlanAPInfos) {
100 for (var wlan in wlanAPInfos) {
101 var wlansplit=SplitWlan(wlan);
102 connectioninfo_table_entries.push([
103 '<nobr>' + '%h'.format(wlansplit[0]) + '</nobr>',
104 '<nobr>' + '%h'.format(wlansplit[1]) + '</nobr>',
105 '%h'.format(wlanAPInfos[wlan]['bssid']),
106 '%h'.format(wlanAPInfos[wlan]['ssid']),
107 '%h'.format(wlanAPInfos[wlan]['freq']),
108 '%h'.format(wlanAPInfos[wlan]['n_assoc']),
109 '%h'.format(wlanAPInfos[wlan]['noise']),
110 '%h'.format(wlanAPInfos[wlan]['load']),
111 '%h'.format(wlanAPInfos[wlan]['max_assoc']),
112 typeof wlanAPInfos[wlan]['roam_events']['source'] !== 'undefined' ? '%h'.format(wlanAPInfos[wlan]['roam_events']['source']) : '',
113 typeof wlanAPInfos[wlan]['roam_events']['target'] !== 'undefined' ? '%h'.format(wlanAPInfos[wlan]['roam_events']['target']) : ''
114 ]);
115 }
116 };
117
118 function tootltip(mac, IP, hostname) {
119 var body= E([]);
120 body.appendChild(E('div', '%h'.format(mac)));
121 if (typeof IP !== 'undefined') {
122 for (var IPaddr in IP['ipaddrs']) body.appendChild(E('div', '%h'.format(IP['ipaddrs'][IPaddr])));
123 for (var IPaddr in IP['ip6addrs']) body.appendChild(E('div', '%h'.format(IP['ip6addrs'][IPaddr])));;
124 }
125 if (hostname !== '') {
126 body.appendChild(E('div', '%h'.format(hostname)));
127 }
128 return body;
129 }
130
131 function collectWlanAPInfos(compactconnectioninfo_table_entries, wlanAPInfos) {
132 for (var wlan in wlanAPInfos) {
133 var hostl = E([]);
134 for (var mac in Clients) {
135 if (typeof Clients[mac] !== 'undefined')
136 if (typeof Clients[mac][wlan] !== 'undefined')
137 if (String(Clients[mac][wlan]['connected']).valueOf() === 'true') {
138 var foundname = mac;
139 var IP = '';
140 var hostname = '';
141 var macUp = mac.toUpperCase();
142 if (typeof Hosts[macUp] !== 'undefined') {
143 if ((typeof Hosts[macUp]['ipaddrs'][0] !== 'undefined') && (String(Hosts[macUp]['ipaddrs'][0]).length > 0)) {
144 IP = Hosts[macUp]['ipaddrs'][0];
145 foundname = IP;
146 }
147 if ((typeof Hosts[macUp]['name'] !== 'undefined') && (String(Hosts[macUp]['name']).length > 0)) {
148 hostname = Hosts[macUp]['name'];
149 foundname = hostname;
150 }
151 }
152 hostl.appendChild(
153 E('span', { 'class': 'cbi-tooltip-container' }, [
154 '%h\u2003'.format(foundname),
155 E('div', { 'class': 'cbi-tooltip' }, tootltip(mac, Hosts[macUp], hostname))
156 ])
157 );
158 }
159 }
160 var wlansplit=SplitWlan(wlan);
161 compactconnectioninfo_table_entries.push([
162 '<nobr>' + '%h'.format(wlansplit[0]) + '</nobr>',
163 '<nobr>' + '%h'.format(wlansplit[1]) + '</nobr>',
164 '%h'.format(wlanAPInfos[wlan]['ssid']),
165 '%h'.format(wlanAPInfos[wlan]['freq']),
166 '%h'.format(wlanAPInfos[wlan]['load']),
167 '%h'.format(wlanAPInfos[wlan]['n_assoc']),
168 hostl
169 ]);
170 }
171 };
172
173 var callNetworkRrdnsLookup = rpc.declare({
174 object: 'network.rrdns',
175 method: 'lookup',
176 params: [ 'addrs', 'timeout', 'limit' ],
177 expect: { '': {} }
178 });
179
180
181 function collectRemoteHosts (remotehosttableentries,Remotehosts) {
182 const getUndefinedDnsCacheIPs = (Remotehosts, dns_cache) =>
183 Object.keys(Remotehosts).filter(IPaddr => !dns_cache.hasOwnProperty(IPaddr));
184
185 var ipAddrs = getUndefinedDnsCacheIPs(Remotehosts, dns_cache);
186
187 L.resolveDefault(callNetworkRrdnsLookup(ipAddrs, 1000, 1000), {}).then(function(replies) {
188 for (var address of ipAddrs) {
189 if (!address)
190 continue;
191 if (replies[address]) {
192 dns_cache[address] = replies[address];
193 continue;
194 } else {
195 dns_cache[address]=Hosts[
196 Object.keys(Hosts).find(mac =>
197 ((typeof Hosts[mac]['name'] !== 'undefined') &&
198 ((Object.keys(Hosts[mac]['ip6addrs']).find(IPaddr2 => (address === Hosts[mac]['ip6addrs'][IPaddr2]))) ||
199 (Object.keys(Hosts[mac]['ipaddrs']).find(IPaddr2 => (address === Hosts[mac]['ipaddrs'][IPaddr2])))))
200 )
201 ]['name'];
202 }
203 }
204 });
205
206 for (var IPaddr in Remotehosts) {
207 remotehosttableentries.push([IPaddr,'%h'.format(dns_cache[IPaddr]),'%h'.format(Remotehosts[IPaddr]['id'])]);
208 }
209 }
210
211
212 var Clientinfooverview = form.DummyValue.extend({
213 renderWidget: function () {
214 var body = E([
215 E('h3', _('Remote hosts'))
216 ]);
217 var remotehost_table = E('table', {'class': 'table cbi-section-table', 'id': 'remotehost_table'}, [
218 E('tr', {'class': 'tr table-titles'}, [
219 E('th', {'class': 'th'}, _('IP address')),
220 E('th', {'class': 'th'}, _('Hostname')),
221 E('th', {'class': 'th'}, _('Identifier'))
222 ])
223 ]);
224 var remotehosttableentries = [];
225 collectRemoteHosts(remotehosttableentries,Remotehosts);
226 cbi_update_table(remotehost_table, remotehosttableentries, E('em', _('No data')));
227 body.appendChild(remotehost_table);
228 body.appendChild(
229 E('h3', _('Client list'))
230 );
231 var connectioninfo_table = E('table', {'class': 'table cbi-section-table', 'id': 'connectioninfo_table'}, [
232 E('tr', {'class': 'tr table-titles'}, [
233 E('th', {'class': 'th'}, _('AP','Name or IP address of access point')),
234 E('th', {'class': 'th'}, _('Interface name','interface name in usteer overview')),
235 E('th', {'class': 'th'}, _('BSSID')),
236 E('th', {'class': 'th'}, _('SSID')),
237 E('th', {'class': 'th'}, _('Frequency','BSS operating frequency in usteer overview')),
238 E('th', {'class': 'th'}, _('N','Number of associated clients in usteer overview')),
239 E('th', {'class': 'th'}, _('Noise','Channel noise in usteer overview')),
240 E('th', {'class': 'th'}, _('Load','Channel load in usteer overview')),
241 E('th', {'class': 'th'}, _('Max assoc','Max associated clients in usteer overview')),
242 E('th', {'class': 'th'}, _('Roam src','Roam source in usteer overview')),
243 E('th', {'class': 'th'}, _('Roam tgt','Roam target in usteer overview'))
244 ])
245 ]);
246 var connectioninfo_table_entries = [];
247 collectWlanAPInfoEntries(connectioninfo_table_entries, Localinfo);
248 collectWlanAPInfoEntries(connectioninfo_table_entries, Remoteinfo);
249
250 cbi_update_table(connectioninfo_table, connectioninfo_table_entries, E('em', _('No data')));
251 body.appendChild(connectioninfo_table);
252 var compactconnectioninfo_table = E('table', {'class': 'table cbi-section-table','id': 'compactconnectioninfo_table'}, [
253 E('tr', {'class': 'tr table-titles'}, [
254 E('th', {'class': 'th'}, _('AP','Name or IP address of access point')),
255 E('th', {'class': 'th'}, _('Interface name','interface name in usteer overview')),
256 E('th', {'class': 'th'}, _('SSID')),
257 E('th', {'class': 'th'}, _('Frequency', 'BSS operating frequency in usteer overview')),
258 E('th', {'class': 'th'}, _('Load', 'Channel load in usteer overview')),
259 E('th', {'class': 'th'}, _('N', 'Number of associated clients in usteer overview')),
260 E('th', {'class': 'th'}, _('Host', 'host hint in usteer overview'))
261 ])
262 ]);
263 var compactconnectioninfo_table_entries = [];
264 collectWlanAPInfos(compactconnectioninfo_table_entries, Localinfo);
265 collectWlanAPInfos(compactconnectioninfo_table_entries, Remoteinfo);
266 cbi_update_table(compactconnectioninfo_table, compactconnectioninfo_table_entries, E('em', _('No data')));
267 body.appendChild(compactconnectioninfo_table);
268 return E('div', {'class': 'cbi-section cbi-tblsection'}, [body]);
269 }
270 });
271
272 var Settingstitle = form.DummyValue.extend({
273 renderWidget: function () {
274 var body = E([
275 E('h3', _('Settings')),
276 E('div',
277 _('The first four options below are mandatory.') + ' ' +
278 _('Also be sure to enable rrm reports, 80211kv, etc.') + ' ' +
279 _('See <a %s>documentation</a>').format('href="https://openwrt.org/docs/guide-user/network/wifi/usteer"')
280 ),
281 ]);
282 return E('div', [body]);
283 }
284 });
285
286 var footerdata;
287 var Settingsfooter = form.DummyValue.extend({
288 renderWidget: function () {
289 var body = E([
290 E('body', footerdata),
291 ]);
292 return E('div', {'style': 'width:100%'}, [footerdata]);
293 }
294 });
295
296
297 return view.extend({
298 callHostHints: rpc.declare({
299 object: 'luci-rpc',
300 method: 'getHostHints',
301 expect: {'': {}}
302 }),
303 callGetRemotehosts: rpc.declare({
304 object: 'usteer',
305 method: 'remote_hosts',
306 expect: {'': {}}
307 }),
308 callGetRemoteinfo: rpc.declare({
309 object: 'usteer',
310 method: 'remote_info',
311 expect: {'': {}}
312 }),
313 callGetLocalinfo: rpc.declare({
314 object: 'usteer',
315 method: 'local_info',
316 expect: {'': {}}
317 }),
318 callGetClients: rpc.declare({
319 object: 'usteer',
320 method: 'get_clients',
321 expect: {'': {}}
322 }),
323 load: function () {
324 return Promise.all([
325 rpc.list('usteer'),
326 this.callHostHints().catch (function (){return null;}),
327 this.callGetRemotehosts().catch (function (){return null;}),
328 this.callGetRemoteinfo().catch (function (){return null;}),
329 this.callGetLocalinfo().catch (function (){return null;}),
330 this.callGetClients().catch (function (){return null;}),
331 network.getWifiNetworks()
332 ]);
333 },
334
335 poll_status: function(nodes, data) {
336
337 Hosts = data[1];
338 Remotehosts = data[2];
339 Remoteinfo = data[3];
340 Localinfo = data[4];
341 Clients = data[5];
342
343 var remotehosttableentries = [];
344 collectRemoteHosts(remotehosttableentries,Remotehosts);
345 cbi_update_table(nodes.querySelector('#remotehost_table'), remotehosttableentries, E('em', _('No data')));
346
347 var connectioninfo_table_entries = [];
348 collectWlanAPInfoEntries(connectioninfo_table_entries, Localinfo);
349 collectWlanAPInfoEntries(connectioninfo_table_entries, Remoteinfo);
350 cbi_update_table(nodes.querySelector('#connectioninfo_table'), connectioninfo_table_entries, E('em', _('No data')));
351
352 var compactconnectioninfo_table_entries = [];
353 collectWlanAPInfos(compactconnectioninfo_table_entries, Localinfo);
354 collectWlanAPInfos(compactconnectioninfo_table_entries, Remoteinfo);
355 cbi_update_table(nodes.querySelector('#compactconnectioninfo_table'), compactconnectioninfo_table_entries, E('em', _('No data')));
356
357 for (var mac in Clients) {
358 var macn = mac.toUpperCase().replace(/:/g,'');
359 var client_table_entries = [];
360 collectHearingClient(client_table_entries, mac);
361 cbi_update_table(nodes.querySelector('#client_table'+macn), client_table_entries, E('em', _('No data')));
362 }
363 return;
364 },
365
366 render: function (data) {
367 var m, s, o;
368
369 if (!('usteer' in data[0])) {
370 m = new form.Map('usteer', _('Usteer'),
371 _('Usteer is not running. Make sure it is installed and running.') +' '+
372 _('An incorrect parameter can cause usteer to fail to start up.') +' '+
373 _('To start it running try %s').format('<code>/etc/init.d/usteer start</code>')
374 );
375 }
376
377 else {
378 m = new form.Map('usteer', _('Usteer'));
379 }
380
381 Hosts = data[1];
382 Remotehosts = data[2];
383 Remoteinfo = data[3];
384 Localinfo = data[4];
385 Clients = data[5];
386 WifiNetworks = data[6];
387
388 s = m.section(form.TypedSection);
389 s.anonymous = true;
390 s.tab('status', _('Status'));
391 s.tab('hearingmap', _('Hearing map'));
392 s.tab('settings', _('Settings'));
393
394 if (('usteer' in data[0])) {
395 o = s.taboption('status', Clientinfooverview);
396 o.readonly = true;
397
398 o = s.taboption('hearingmap', HearingMap);
399 o.readonly = true;
400 }
401
402 o = s.taboption('settings', Settingstitle);
403 o.readonly = true;
404
405 o = s.taboption('settings', widgets.NetworkSelect, 'network', _('Network'), _('The network interface for inter-AP communication'));
406
407 o = s.taboption('settings', form.Flag, 'syslog', _('Log messages to syslog'),_('default true'));
408 o.default = '1';
409 o.rmempty = false;
410
411 o = s.taboption('settings', form.Flag, 'local_mode', _('Local mode'), _('Disable network communication')+' ('+_('default false')+')');
412 o.rmempty = false;
413
414 o = s.taboption('settings', form.Flag, 'ipv6', _('IPv6 mode'), _('Use IPv6 for remote exchange')+' ('+_('default false')+')');
415 o.rmempty = false;
416
417
418 o = s.taboption('settings', form.ListValue, 'debug_level', _('Debug level'));
419 o.value('0', _('Fatal'));
420 o.value('1', _('Info'));
421 o.value('2', _('Verbose'));
422 o.value('3', _('Some debug'));
423 o.value('4', _('Network packet info'));
424 o.value('5', _('All debug messages'));
425 o.rmempty = false;
426 o.editable = true;
427
428 o = s.taboption('settings', form.Value, 'max_neighbour_reports', _('Max neighbour reports'), _('Maximum number of neighbor reports set for a node'));
429 o.optional = true;
430 o.placeholder = 8;
431 o.datatype = 'uinteger';
432
433 o = s.taboption('settings', form.Value, 'sta_block_timeout', _('Sta block timeout'), _('Maximum amount of time (ms) a station may be blocked due to policy decisions'));
434 o.optional = true;
435 o.placeholder = 30000;
436 o.datatype = 'uinteger';
437
438 o = s.taboption('settings', form.Value, 'local_sta_timeout', _('Local sta timeout'), _('Maximum amount of time (ms) a local unconnected station is tracked'));
439 o.optional = true;
440 o.placeholder = 120000;
441 o.datatype = 'uinteger';
442
443 o = s.taboption('settings', form.Value, 'measurement_report_timeout', _('Measurement report timeout'), _('Maximum amount of time (ms) a measurement report is stored'));
444 o.optional = true;
445 o.placeholder = 120000;
446 o.datatype = 'uinteger';
447
448 o = s.taboption('settings', form.Value, 'local_sta_update', _('Local sta update'), _('Local station information update interval (ms)'));
449 o.optional = true;
450 o.placeholder = 1000;
451 o.datatype = 'uinteger';
452
453 o = s.taboption('settings', form.Value, 'max_retry_band', _('Max retry band'), _('Maximum number of consecutive times a station may be blocked by policy'));
454 o.optional = true;
455 o.placeholder = 5;
456 o.datatype = 'uinteger';
457
458 o = s.taboption('settings', form.Value, 'seen_policy_timeout', _('Seen policy timeout'), _('Maximum idle time of a station entry (ms) to be considered for policy decisions'));
459 o.optional = true;
460 o.placeholder = 30000;
461 o.datatype = 'uinteger';
462
463 o = s.taboption('settings', form.Value, 'load_balancing_threshold', _('Load balancing threshold'), _('Minimum number of stations delta between APs before load balancing policy is active'));
464 o.optional = true;
465 o.placeholder = 0;
466 o.datatype = 'uinteger';
467
468 o = s.taboption('settings', form.Value, 'band_steering_threshold', _('Band steering threshold'), _('Minimum number of stations delta between bands before band steering policy is active'));
469 o.optional = true;
470 o.placeholder = 5;
471 o.datatype = 'uinteger';
472
473 o = s.taboption('settings', form.Value, 'remote_update_interval', _('Remote update interval'), _('Interval (ms) between sending state updates to other APs'));
474 o.optional = true;
475 o.placeholder = 1000;
476 o.datatype = 'uinteger';
477
478 o = s.taboption('settings', form.Value, 'remote_node_timeout', _('Remote node timeout'), _('Number of remote update intervals after which a remote-node is deleted'));
479 o.optional = true;
480 o.placeholder = 10;
481 o.datatype = 'uinteger';
482
483 o = s.taboption('settings', form.Flag, 'assoc_steering', _('Assoc steering'), _('Allow rejecting assoc requests for steering purposes')+' ('+_('default false')+')');
484 o.optional = true;
485
486 o = s.taboption('settings', form.Flag, 'probe_steering', _('Probe steering'), _('Allow ignoring probe requests for steering purposes')+' ('+_('default false')+')');
487 o.optional = true;
488
489 o = s.taboption('settings', form.Value, 'min_connect_snr', _('Min connect SNR'), _('Minimum signal-to-noise ratio or signal level (dBm) to allow connections'));
490 o.optional = true;
491 o.placeholder = 0;
492 o.datatype = 'integer';
493
494 o = s.taboption('settings', form.Value, 'min_snr', _('Min SNR'), _('Minimum signal-to-noise ratio or signal level (dBm) to remain connected'));
495 o.optional = true;
496 o.placeholder = 0;
497 o.datatype = 'integer';
498
499 o = s.taboption('settings', form.Value, 'min_snr_kick_delay', _('Min SNR kick delay'), _('Timeout after which a station with SNR < min_SNR will be kicked'));
500 o.optional = true;
501 o.placeholder = 5000;
502 o.datatype = 'uinteger';
503
504 o = s.taboption('settings', form.Value, 'steer_reject_timeout', _('Steer reject timeout'), _('Timeout (ms) for which a client will not be steered after rejecting a BSS-transition-request'));
505 o.optional = true;
506 o.placeholder = 60000;
507 o.datatype = 'uinteger';
508
509 o = s.taboption('settings', form.Value, 'roam_process_timeout', _('Roam process timeout'), _('Timeout (in ms) after which a association following a disassociation is not seen as a roam'));
510 o.optional = true;
511 o.placeholder = 5000;
512 o.datatype = 'uinteger';
513
514 o = s.taboption('settings', form.Value, 'roam_scan_snr', _('Roam scan SNR'), _('Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger client scans for roam'));
515 o.optional = true;
516 o.placeholder = 0;
517 o.datatype = 'integer';
518
519 o = s.taboption('settings', form.Value, 'roam_scan_tries', _('Roam scan tries'), _('Maximum number of client roaming scan trigger attempts'));
520 o.optional = true;
521 o.placeholder = 3;
522 o.datatype = 'uinteger';
523
524 o = s.taboption('settings', form.Value, 'roam_scan_timeout', _('Roam scan timeout'),
525 _('Retry scanning when roam_scan_tries is exceeded after this timeout (in ms).') +
526 _(' In case this option is disabled, the client is kicked instead')
527 );
528 o.optional = true;
529 o.placeholder = 0;
530 o.datatype = 'uinteger';
531
532 o = s.taboption('settings', form.Value, 'roam_scan_interval', _('Roam scan interval'), _('Minimum time (ms) between client roaming scan trigger attempts'));
533 o.optional = true;
534 o.placeholder = 10000;
535 o.datatype = 'uinteger';
536
537 o = s.taboption('settings', form.Value, 'roam_trigger_snr', _('Roam trigger SNR'), _('Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger forced client roaming'));
538 o.optional = true;
539 o.placeholder = 0;
540 o.datatype = 'integer';
541
542 o = s.taboption('settings', form.Value, 'roam_trigger_interval', _('Roam trigger interval'), _('Minimum time (ms) between client roaming trigger attempts'));
543 o.optional = true;
544 o.placeholder = 60000;
545 o.datatype = 'uinteger';
546
547 o = s.taboption('settings', form.Value, 'roam_kick_delay', _('Roam kick delay'), _('Timeout (ms) for client roam requests. usteer will kick the client after this times out.'));
548 o.optional = true;
549 o.placeholder = 10000;
550 o.datatype = 'uinteger';
551
552 o = s.taboption('settings', form.Value, 'signal_diff_threshold', _('Signal diff threshold'), _('Minimum signal strength difference until AP steering policy is active'));
553 o.optional = true;
554 o.placeholder = 0;
555 o.datatype = 'uinteger';
556
557 o = s.taboption('settings', form.Value, 'initial_connect_delay', _('Initial connect delay'), _('Initial delay (ms) before responding to probe requests (to allow other APs to see packets as well)'));
558 o.optional = true;
559 o.placeholder = 0;
560 o.datatype = 'uinteger';
561
562 o = s.taboption('settings', form.Flag, 'load_kick_enabled', _('Load kick enabled'), _('Enable kicking client on excessive channel load')+' ('+_('default false')+')');
563 o.optional = true;
564
565 o = s.taboption('settings', form.Value, 'load_kick_threshold', _('Load kick threshold'), _('Minimum channel load (%) before kicking clients'));
566 o.optional = true;
567 o.placeholder = 75;
568 o.datatype = 'uinteger';
569
570 o = s.taboption('settings', form.Value, 'load_kick_delay', _('Load kick delay'), _('Minimum amount of time (ms) that channel load is above threshold before starting to kick clients'));
571 o.optional = true;
572 o.placeholder = 10000;
573 o.datatype = 'uinteger';
574
575 o = s.taboption('settings', form.Value, 'load_kick_min_clients', _('Load kick min clients'), _('Minimum number of connected clients before kicking based on channel load'));
576 o.optional = true;
577 o.placeholder = 10;
578 o.datatype = 'uinteger';
579
580 o = s.taboption('settings', form.Value, 'load_kick_reason_code', _('Load kick reason code'),
581 _('Reason code on client kick based on channel load.') + ' Default: WLAN_REASON_DISASSOC_AP_BUSY)'
582 );
583 o.optional = true;
584 o.placeholder = 5;
585 o.datatype = 'uinteger';
586
587 o = s.taboption('settings', form.Value, 'band_steering_interval', _('Band steering interval'), _('Attempting to steer clients to a higher frequency-band every n ms. A value of 0 disables band-steering.'));
588 o.optional = true;
589 o.placeholder = 120000;
590 o.datatype = 'uinteger';
591
592 o = s.taboption('settings', form.Value, 'band_steering_min_snr', _('Band steering min SNR'), _('Minimal SNR or absolute signal a device has to maintain over band_steering_interval to be steered to a higher frequency band.'));
593 o.optional = true;
594 o.placeholder = -60;
595 o.datatype = 'integer';
596
597 o = s.taboption('settings', form.Value, 'link_measurement_interval', _('Link measurement interval'),
598 _('Interval (ms) the device is sent a link-measurement request to help assess the bi-directional link quality.') +
599 _('Setting the interval to 0 disables link-measurements.')
600 );
601 o.optional = true;
602 o.placeholder = 30000;
603 o.datatype = 'uinteger';
604
605 o = s.taboption('settings', form.Value, 'node_up_script', _('Node up script'), _('Script to run after bringing up a node'));
606 o.optional = true;
607 o.datatype = 'string';
608
609 o = s.taboption('settings', form.MultiValue, 'event_log_types', _('Event log types'), _('Message types to include in log.'));
610 o.value('probe_req_accept');
611 o.value('probe_req_deny');
612 o.value('auth_req_accept');
613 o.value('auth_req_deny');
614 o.value('assoc_req_accept');
615 o.value('assoc_req_deny');
616 o.value('load_kick_trigger');
617 o.value('load_kick_reset');
618 o.value('load_kick_min_clients');
619 o.value('load_kick_no_client');
620 o.value('load_kick_client');
621 o.value('signal_kick');
622 o.optional = true;
623 o.datatype = 'list(string)';
624
625 o = s.taboption('settings', form.DynamicList, 'ssid_list', _('SSID list'), _('List of SSIDs to enable steering on')+' ('+_('empty means all')+')');
626 WifiNetworks.forEach(function (wifiNetwork) {
627 if (wifiNetwork.getSSID() && (!o.keylist || o.keylist.indexOf(wifiNetwork.getSSID()) === -1)) {
628 o.value(wifiNetwork.getSSID())
629 }
630 });
631 o.optional = true;
632 o.datatype = 'list(string)';
633
634 footerdata = this.super('addFooter', []);
635 o = s.taboption('settings', Settingsfooter);
636 o.readonly = true;
637
638 return m.render().then(L.bind(function(m, nodes) {
639 poll.add(L.bind(function() {
640 return Promise.all([
641 rpc.list('usteer'),
642 this.callHostHints().catch (function (){return null;}),
643 this.callGetRemotehosts().catch (function (){return null;}),
644 this.callGetRemoteinfo().catch (function (){return null;}),
645 this.callGetLocalinfo().catch (function (){return null;}),
646 this.callGetClients().catch (function (){return null;})
647 ]).then(L.bind(this.poll_status, this, nodes));
648 }, this), 5);
649 return nodes;
650 }, this, m));
651 },
652
653
654 addFooter: function () {
655 return null;
656 },
657 });