wireless.js 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073
  1. 'use strict';
  2. 'require view';
  3. 'require dom';
  4. 'require poll';
  5. 'require fs';
  6. 'require ui';
  7. 'require rpc';
  8. 'require uci';
  9. 'require form';
  10. 'require network';
  11. 'require firewall';
  12. 'require tools.widgets as widgets';
  13. var isReadonlyView = !L.hasViewPermission();
  14. function count_changes(section_id) {
  15. var changes = ui.changes.changes, n = 0;
  16. if (!L.isObject(changes))
  17. return n;
  18. if (Array.isArray(changes.wireless))
  19. for (var i = 0; i < changes.wireless.length; i++)
  20. n += (changes.wireless[i][1] == section_id);
  21. return n;
  22. }
  23. function render_radio_badge(radioDev) {
  24. return E('span', { 'class': 'ifacebadge' }, [
  25. E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }),
  26. ' ',
  27. radioDev.getName()
  28. ]);
  29. }
  30. function render_signal_badge(signalPercent, signalValue, noiseValue, wrap, mode) {
  31. var icon, title, value;
  32. if (signalPercent < 0)
  33. icon = L.resource('icons/signal-none.png');
  34. else if (signalPercent == 0)
  35. icon = L.resource('icons/signal-0.png');
  36. else if (signalPercent < 25)
  37. icon = L.resource('icons/signal-0-25.png');
  38. else if (signalPercent < 50)
  39. icon = L.resource('icons/signal-25-50.png');
  40. else if (signalPercent < 75)
  41. icon = L.resource('icons/signal-50-75.png');
  42. else
  43. icon = L.resource('icons/signal-75-100.png');
  44. if (signalValue != null && signalValue != 0) {
  45. if (noiseValue != null && noiseValue != 0) {
  46. value = '%d/%d\xa0%s'.format(signalValue, noiseValue, _('dBm'));
  47. title = '%s: %d %s / %s: %d %s / %s %d'.format(
  48. _('Signal'), signalValue, _('dBm'),
  49. _('Noise'), noiseValue, _('dBm'),
  50. _('SNR'), signalValue - noiseValue);
  51. }
  52. else {
  53. value = '%d\xa0%s'.format(signalValue, _('dBm'));
  54. title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm'));
  55. }
  56. }
  57. else if (signalPercent > -1) {
  58. switch (mode) {
  59. case 'ap':
  60. title = _('No client associated');
  61. break;
  62. case 'sta':
  63. case 'adhoc':
  64. case 'mesh':
  65. title = _('Not associated');
  66. break;
  67. default:
  68. title = _('No RX signal');
  69. }
  70. if (noiseValue != null && noiseValue != 0) {
  71. value = '---/%d\x0a%s'.format(noiseValue, _('dBm'));
  72. title = '%s / %s: %d %s'.format(title, _('Noise'), noiseValue, _('dBm'));
  73. }
  74. else {
  75. value = '---\xa0%s'.format(_('dBm'));
  76. }
  77. }
  78. else {
  79. value = E('em', {}, E('small', {}, [ _('disabled') ]));
  80. title = _('Interface is disabled');
  81. }
  82. return E('div', {
  83. 'class': wrap ? 'center' : 'ifacebadge',
  84. 'title': title,
  85. 'data-signal': signalValue,
  86. 'data-noise': noiseValue
  87. }, [
  88. E('img', { 'src': icon }),
  89. E('span', {}, [
  90. wrap ? E('br') : ' ',
  91. value
  92. ])
  93. ]);
  94. }
  95. function render_network_badge(radioNet) {
  96. return render_signal_badge(
  97. radioNet.isUp() ? radioNet.getSignalPercent() : -1,
  98. radioNet.getSignal(), radioNet.getNoise(), false, radioNet.getMode());
  99. }
  100. function render_radio_status(radioDev, wifiNets) {
  101. var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
  102. node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
  103. channel, frequency, bitrate;
  104. for (var i = 0; i < wifiNets.length; i++) {
  105. channel = channel || wifiNets[i].getChannel();
  106. frequency = frequency || wifiNets[i].getFrequency();
  107. bitrate = bitrate || wifiNets[i].getBitRate();
  108. }
  109. if (radioDev.isUp())
  110. L.itemlist(node.lastElementChild, [
  111. _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
  112. _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
  113. ], ' | ');
  114. else
  115. node.lastElementChild.appendChild(E('em', _('Device is not active')));
  116. return node;
  117. }
  118. function render_network_status(radioNet) {
  119. var mode = radioNet.getActiveMode(),
  120. bssid = radioNet.getActiveBSSID(),
  121. channel = radioNet.getChannel(),
  122. disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
  123. is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
  124. is_mesh = (radioNet.getMode() == 'mesh'),
  125. changecount = count_changes(radioNet.getName()),
  126. status_text = null;
  127. if (changecount)
  128. status_text = E('a', {
  129. href: '#',
  130. click: L.bind(ui.changes.displayChanges, ui.changes)
  131. }, _('Interface has %d pending changes').format(changecount));
  132. else if (!is_assoc)
  133. status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
  134. return L.itemlist(E('div'), [
  135. is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
  136. _('Mode'), mode,
  137. _('BSSID'), (!changecount && is_assoc) ? bssid : null,
  138. _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
  139. null, status_text
  140. ], [ ' | ', E('br') ]);
  141. }
  142. function render_modal_status(node, radioNet) {
  143. var mode = radioNet.getActiveMode(),
  144. noise = radioNet.getNoise(),
  145. bssid = radioNet.getActiveBSSID(),
  146. channel = radioNet.getChannel(),
  147. disabled = (radioNet.get('disabled') == '1'),
  148. is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
  149. if (node == null)
  150. node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
  151. dom.content(node.firstElementChild, render_signal_badge(
  152. disabled ? -1 : radioNet.getSignalPercent(),
  153. radioNet.getSignal(), noise, true, radioNet.getMode()));
  154. L.itemlist(node.lastElementChild, [
  155. _('Mode'), mode,
  156. _('SSID'), radioNet.getSSID() || '?',
  157. _('BSSID'), is_assoc ? bssid : null,
  158. _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
  159. _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
  160. _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
  161. _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
  162. _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
  163. _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
  164. _('Country'), is_assoc ? radioNet.getCountryCode() : null
  165. ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
  166. if (!is_assoc)
  167. dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
  168. return node;
  169. }
  170. function format_wifirate(rate) {
  171. var s = '%.1f\xa0%s, %d\xa0%s'.format(rate.rate / 1000, _('Mbit/s'), rate.mhz, _('MHz')),
  172. ht = rate.ht, vht = rate.vht,
  173. mhz = rate.mhz, nss = rate.nss,
  174. mcs = rate.mcs, sgi = rate.short_gi;
  175. if (ht || vht) {
  176. if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
  177. if (nss) s += ', VHT-NSS\xa0%d'.format(nss);
  178. if (ht) s += ', MCS\xa0%s'.format(mcs);
  179. if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0');
  180. }
  181. return s;
  182. }
  183. function radio_restart(id, ev) {
  184. var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
  185. dsc = row.querySelector('[data-name="_stat"] > div'),
  186. btn = row.querySelector('.cbi-section-actions button');
  187. btn.blur();
  188. btn.classList.add('spinning');
  189. btn.disabled = true;
  190. dsc.setAttribute('restart', '');
  191. dom.content(dsc, E('em', _('Device is restarting…')));
  192. }
  193. function network_updown(id, map, ev) {
  194. var radio = uci.get('wireless', id, 'device'),
  195. disabled = (uci.get('wireless', id, 'disabled') == '1') ||
  196. (uci.get('wireless', radio, 'disabled') == '1');
  197. if (disabled) {
  198. uci.unset('wireless', id, 'disabled');
  199. uci.unset('wireless', radio, 'disabled');
  200. }
  201. else {
  202. uci.set('wireless', id, 'disabled', '1');
  203. var all_networks_disabled = true,
  204. wifi_ifaces = uci.sections('wireless', 'wifi-iface');
  205. for (var i = 0; i < wifi_ifaces.length; i++) {
  206. if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
  207. all_networks_disabled = false;
  208. break;
  209. }
  210. }
  211. if (all_networks_disabled)
  212. uci.set('wireless', radio, 'disabled', '1');
  213. }
  214. return map.save().then(function() {
  215. ui.changes.apply()
  216. });
  217. }
  218. function next_free_sid(offset) {
  219. var sid = 'wifinet' + offset;
  220. while (uci.get('wireless', sid))
  221. sid = 'wifinet' + (++offset);
  222. return sid;
  223. }
  224. function add_dependency_permutations(o, deps) {
  225. var res = null;
  226. for (var key in deps) {
  227. if (!deps.hasOwnProperty(key) || !Array.isArray(deps[key]))
  228. continue;
  229. var list = deps[key],
  230. tmp = [];
  231. for (var j = 0; j < list.length; j++) {
  232. for (var k = 0; k < (res ? res.length : 1); k++) {
  233. var item = (res ? Object.assign({}, res[k]) : {});
  234. item[key] = list[j];
  235. tmp.push(item);
  236. }
  237. }
  238. res = tmp;
  239. }
  240. for (var i = 0; i < (res ? res.length : 0); i++)
  241. o.depends(res[i]);
  242. }
  243. var CBIWifiFrequencyValue = form.Value.extend({
  244. callFrequencyList: rpc.declare({
  245. object: 'iwinfo',
  246. method: 'freqlist',
  247. params: [ 'device' ],
  248. expect: { results: [] }
  249. }),
  250. load: function(section_id) {
  251. return Promise.all([
  252. network.getWifiDevice(section_id),
  253. this.callFrequencyList(section_id)
  254. ]).then(L.bind(function(data) {
  255. this.channels = {
  256. '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
  257. '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
  258. };
  259. for (var i = 0; i < data[1].length; i++)
  260. this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
  261. data[1][i].channel,
  262. '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
  263. !data[1][i].restricted
  264. );
  265. var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
  266. .reduce(function(o, v) { o[v] = true; return o }, {});
  267. this.modes = [
  268. '', 'Legacy', true,
  269. 'n', 'N', hwmodelist.n,
  270. 'ac', 'AC', hwmodelist.ac
  271. ];
  272. var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
  273. .reduce(function(o, v) { o[v] = true; return o }, {});
  274. this.htmodes = {
  275. '': [ '', '-', true ],
  276. 'n': [
  277. 'HT20', '20 MHz', htmodelist.HT20,
  278. 'HT40', '40 MHz', htmodelist.HT40
  279. ],
  280. 'ac': [
  281. 'VHT20', '20 MHz', htmodelist.VHT20,
  282. 'VHT40', '40 MHz', htmodelist.VHT40,
  283. 'VHT80', '80 MHz', htmodelist.VHT80,
  284. 'VHT160', '160 MHz', htmodelist.VHT160
  285. ]
  286. };
  287. this.bands = {
  288. '': [
  289. '11g', '2.4 GHz', this.channels['11g'].length > 3,
  290. '11a', '5 GHz', this.channels['11a'].length > 3
  291. ],
  292. 'n': [
  293. '11g', '2.4 GHz', this.channels['11g'].length > 3,
  294. '11a', '5 GHz', this.channels['11a'].length > 3
  295. ],
  296. 'ac': [
  297. '11a', '5 GHz', true
  298. ]
  299. };
  300. }, this));
  301. },
  302. setValues: function(sel, vals) {
  303. if (sel.vals)
  304. sel.vals.selected = sel.selectedIndex;
  305. while (sel.options[0])
  306. sel.remove(0);
  307. for (var i = 0; vals && i < vals.length; i += 3)
  308. if (vals[i+2])
  309. sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
  310. if (vals && !isNaN(vals.selected))
  311. sel.selectedIndex = vals.selected;
  312. sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
  313. sel.vals = vals;
  314. },
  315. toggleWifiMode: function(elem) {
  316. this.toggleWifiHTMode(elem);
  317. this.toggleWifiBand(elem);
  318. },
  319. toggleWifiHTMode: function(elem) {
  320. var mode = elem.querySelector('.mode');
  321. var bwdt = elem.querySelector('.htmode');
  322. this.setValues(bwdt, this.htmodes[mode.value]);
  323. },
  324. toggleWifiBand: function(elem) {
  325. var mode = elem.querySelector('.mode');
  326. var band = elem.querySelector('.band');
  327. this.setValues(band, this.bands[mode.value]);
  328. this.toggleWifiChannel(elem);
  329. },
  330. toggleWifiChannel: function(elem) {
  331. var band = elem.querySelector('.band');
  332. var chan = elem.querySelector('.channel');
  333. this.setValues(chan, this.channels[band.value]);
  334. },
  335. setInitialValues: function(section_id, elem) {
  336. var mode = elem.querySelector('.mode'),
  337. band = elem.querySelector('.band'),
  338. chan = elem.querySelector('.channel'),
  339. bwdt = elem.querySelector('.htmode'),
  340. htval = uci.get('wireless', section_id, 'htmode'),
  341. hwval = uci.get('wireless', section_id, 'hwmode'),
  342. chval = uci.get('wireless', section_id, 'channel');
  343. this.setValues(mode, this.modes);
  344. if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
  345. mode.value = 'ac';
  346. else if (/HT20|HT40/.test(htval))
  347. mode.value = 'n';
  348. else
  349. mode.value = '';
  350. this.toggleWifiMode(elem);
  351. if (/a/.test(hwval))
  352. band.value = '11a';
  353. else
  354. band.value = '11g';
  355. this.toggleWifiBand(elem);
  356. bwdt.value = htval;
  357. chan.value = chval;
  358. return elem;
  359. },
  360. renderWidget: function(section_id, option_index, cfgvalue) {
  361. var elem = E('div');
  362. dom.content(elem, [
  363. E('label', { 'style': 'float:left; margin-right:3px' }, [
  364. _('Mode'), E('br'),
  365. E('select', {
  366. 'class': 'mode',
  367. 'style': 'width:auto',
  368. 'change': L.bind(this.toggleWifiMode, this, elem),
  369. 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
  370. })
  371. ]),
  372. E('label', { 'style': 'float:left; margin-right:3px' }, [
  373. _('Band'), E('br'),
  374. E('select', {
  375. 'class': 'band',
  376. 'style': 'width:auto',
  377. 'change': L.bind(this.toggleWifiBand, this, elem),
  378. 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
  379. })
  380. ]),
  381. E('label', { 'style': 'float:left; margin-right:3px' }, [
  382. _('Channel'), E('br'),
  383. E('select', {
  384. 'class': 'channel',
  385. 'style': 'width:auto',
  386. 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
  387. })
  388. ]),
  389. E('label', { 'style': 'float:left; margin-right:3px' }, [
  390. _('Width'), E('br'),
  391. E('select', {
  392. 'class': 'htmode',
  393. 'style': 'width:auto',
  394. 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
  395. })
  396. ]),
  397. E('br', { 'style': 'clear:left' })
  398. ]);
  399. return this.setInitialValues(section_id, elem);
  400. },
  401. cfgvalue: function(section_id) {
  402. return [
  403. uci.get('wireless', section_id, 'htmode'),
  404. uci.get('wireless', section_id, 'hwmode'),
  405. uci.get('wireless', section_id, 'channel')
  406. ];
  407. },
  408. formvalue: function(section_id) {
  409. var node = this.map.findElement('data-field', this.cbid(section_id));
  410. return [
  411. node.querySelector('.htmode').value,
  412. node.querySelector('.band').value,
  413. node.querySelector('.channel').value
  414. ];
  415. },
  416. write: function(section_id, value) {
  417. uci.set('wireless', section_id, 'htmode', value[0] || null);
  418. uci.set('wireless', section_id, 'hwmode', value[1]);
  419. uci.set('wireless', section_id, 'channel', value[2]);
  420. }
  421. });
  422. var CBIWifiTxPowerValue = form.ListValue.extend({
  423. callTxPowerList: rpc.declare({
  424. object: 'iwinfo',
  425. method: 'txpowerlist',
  426. params: [ 'device' ],
  427. expect: { results: [] }
  428. }),
  429. load: function(section_id) {
  430. return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
  431. this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
  432. this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
  433. this.value('', _('driver default'));
  434. for (var i = 0; i < pwrlist.length; i++)
  435. this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
  436. return form.ListValue.prototype.load.apply(this, [section_id]);
  437. }, this));
  438. },
  439. renderWidget: function(section_id, option_index, cfgvalue) {
  440. var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
  441. widget.firstElementChild.style.width = 'auto';
  442. dom.append(widget, E('span', [
  443. ' - ', _('Current power'), ': ',
  444. E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
  445. this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
  446. ]));
  447. return widget;
  448. }
  449. });
  450. var CBIWifiCountryValue = form.Value.extend({
  451. callCountryList: rpc.declare({
  452. object: 'iwinfo',
  453. method: 'countrylist',
  454. params: [ 'device' ],
  455. expect: { results: [] }
  456. }),
  457. load: function(section_id) {
  458. return this.callCountryList(section_id).then(L.bind(function(countrylist) {
  459. if (Array.isArray(countrylist) && countrylist.length > 0) {
  460. this.value('', _('driver default'));
  461. for (var i = 0; i < countrylist.length; i++)
  462. this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
  463. }
  464. return form.Value.prototype.load.apply(this, [section_id]);
  465. }, this));
  466. },
  467. validate: function(section_id, formvalue) {
  468. if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
  469. return _('Use ISO/IEC 3166 alpha2 country codes.');
  470. return true;
  471. },
  472. renderWidget: function(section_id, option_index, cfgvalue) {
  473. var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
  474. return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
  475. }
  476. });
  477. return view.extend({
  478. poll_status: function(map, data) {
  479. var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
  480. for (var i = 0; i < rows.length; i++) {
  481. var section_id = rows[i].getAttribute('data-sid'),
  482. radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
  483. radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
  484. badge = rows[i].querySelector('[data-name="_badge"] > div'),
  485. stat = rows[i].querySelector('[data-name="_stat"]'),
  486. btns = rows[i].querySelectorAll('.cbi-section-actions button'),
  487. busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
  488. if (radioDev) {
  489. dom.content(badge, render_radio_badge(radioDev));
  490. dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
  491. }
  492. else {
  493. dom.content(badge, render_network_badge(radioNet));
  494. dom.content(stat, render_network_status(radioNet));
  495. }
  496. if (stat.hasAttribute('restart'))
  497. dom.content(stat, E('em', _('Device is restarting…')));
  498. btns[0].disabled = isReadonlyView || busy;
  499. btns[1].disabled = (isReadonlyView && radioDev) || busy;
  500. btns[2].disabled = isReadonlyView || busy;
  501. }
  502. var table = document.querySelector('#wifi_assoclist_table'),
  503. hosts = data[0],
  504. trows = [];
  505. for (var i = 0; i < data[3].length; i++) {
  506. var bss = data[3][i],
  507. name = hosts.getHostnameByMACAddr(bss.mac),
  508. ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
  509. ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
  510. var hint;
  511. if (name && ipv4 && ipv6)
  512. hint = '%s <span class="hide-xs">(%s, %s)</span>'.format(name, ipv4, ipv6);
  513. else if (name && (ipv4 || ipv6))
  514. hint = '%s <span class="hide-xs">(%s)</span>'.format(name, ipv4 || ipv6);
  515. else
  516. hint = name || ipv4 || ipv6 || '?';
  517. var row = [
  518. E('span', {
  519. 'class': 'ifacebadge',
  520. 'data-ifname': bss.network.getIfname(),
  521. 'data-ssid': bss.network.getSSID()
  522. }, [
  523. E('img', {
  524. 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
  525. 'title': bss.radio.getI18n()
  526. }),
  527. E('span', [
  528. ' %s '.format(bss.network.getShortName()),
  529. E('small', '(%s)'.format(bss.network.getIfname()))
  530. ])
  531. ]),
  532. bss.mac,
  533. hint,
  534. render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
  535. E('span', {}, [
  536. E('span', format_wifirate(bss.rx)),
  537. E('br'),
  538. E('span', format_wifirate(bss.tx))
  539. ])
  540. ];
  541. if (bss.network.isClientDisconnectSupported()) {
  542. if (table.firstElementChild.childNodes.length < 6)
  543. table.firstElementChild.appendChild(E('div', { 'class': 'th cbi-section-actions'}));
  544. row.push(E('button', {
  545. 'class': 'cbi-button cbi-button-remove',
  546. 'click': L.bind(function(net, mac, ev) {
  547. dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
  548. ev.currentTarget.classList.add('spinning');
  549. ev.currentTarget.disabled = true;
  550. ev.currentTarget.blur();
  551. net.disconnectClient(mac, true, 5, 60000);
  552. }, this, bss.network, bss.mac),
  553. 'disabled': isReadonlyView || null
  554. }, [ _('Disconnect') ]));
  555. }
  556. else {
  557. row.push('-');
  558. }
  559. trows.push(row);
  560. }
  561. cbi_update_table(table, trows, E('em', _('No information available')));
  562. var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
  563. if (stat)
  564. render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
  565. return network.flushCache();
  566. },
  567. load: function() {
  568. return Promise.all([
  569. uci.changes(),
  570. uci.load('wireless')
  571. ]);
  572. },
  573. checkAnonymousSections: function() {
  574. var wifiIfaces = uci.sections('wireless', 'wifi-iface');
  575. for (var i = 0; i < wifiIfaces.length; i++)
  576. if (wifiIfaces[i]['.anonymous'])
  577. return true;
  578. return false;
  579. },
  580. callUciRename: rpc.declare({
  581. object: 'uci',
  582. method: 'rename',
  583. params: [ 'config', 'section', 'name' ]
  584. }),
  585. render: function() {
  586. if (this.checkAnonymousSections())
  587. return this.renderMigration();
  588. else
  589. return this.renderOverview();
  590. },
  591. handleMigration: function(ev) {
  592. var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
  593. id_offset = 0,
  594. tasks = [];
  595. for (var i = 0; i < wifiIfaces.length; i++) {
  596. if (!wifiIfaces[i]['.anonymous'])
  597. continue;
  598. var new_name = next_free_sid(id_offset);
  599. tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
  600. id_offset = +new_name.substring(7) + 1;
  601. }
  602. return Promise.all(tasks)
  603. .then(L.bind(ui.changes.init, ui.changes))
  604. .then(L.bind(ui.changes.apply, ui.changes));
  605. },
  606. renderMigration: function() {
  607. ui.showModal(_('Wireless configuration migration'), [
  608. E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
  609. E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form <em>wifinet#</em> and the network will be restarted to apply the updated configuration.')),
  610. E('div', { 'class': 'right' },
  611. E('button', {
  612. 'class': 'btn cbi-button-action important',
  613. 'click': ui.createHandlerFn(this, 'handleMigration')
  614. }, _('Continue')))
  615. ]);
  616. },
  617. renderOverview: function() {
  618. var m, s, o;
  619. m = new form.Map('wireless');
  620. m.chain('network');
  621. m.chain('firewall');
  622. s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
  623. s.anonymous = true;
  624. s.addremove = false;
  625. s.load = function() {
  626. return network.getWifiDevices().then(L.bind(function(radios) {
  627. this.radios = radios.sort(function(a, b) {
  628. return a.getName() > b.getName();
  629. });
  630. var tasks = [];
  631. for (var i = 0; i < radios.length; i++)
  632. tasks.push(radios[i].getWifiNetworks());
  633. return Promise.all(tasks);
  634. }, this)).then(L.bind(function(data) {
  635. this.wifis = [];
  636. for (var i = 0; i < data.length; i++)
  637. this.wifis.push.apply(this.wifis, data[i]);
  638. }, this));
  639. };
  640. s.cfgsections = function() {
  641. var rv = [];
  642. for (var i = 0; i < this.radios.length; i++) {
  643. rv.push(this.radios[i].getName());
  644. for (var j = 0; j < this.wifis.length; j++)
  645. if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
  646. rv.push(this.wifis[j].getName());
  647. }
  648. return rv;
  649. };
  650. s.modaltitle = function(section_id) {
  651. var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
  652. return radioNet ? radioNet.getI18n() : _('Edit wireless network');
  653. };
  654. s.lookupRadioOrNetwork = function(section_id) {
  655. var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
  656. if (radioDev)
  657. return radioDev;
  658. var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
  659. if (radioNet)
  660. return radioNet;
  661. return null;
  662. };
  663. s.renderRowActions = function(section_id) {
  664. var inst = this.lookupRadioOrNetwork(section_id), btns;
  665. if (inst.getWifiNetworks) {
  666. btns = [
  667. E('button', {
  668. 'class': 'cbi-button cbi-button-neutral',
  669. 'title': _('Restart radio interface'),
  670. 'click': ui.createHandlerFn(this, radio_restart, section_id)
  671. }, _('Restart')),
  672. E('button', {
  673. 'class': 'cbi-button cbi-button-action important',
  674. 'title': _('Find and join network'),
  675. 'click': ui.createHandlerFn(this, 'handleScan', inst)
  676. }, _('Scan')),
  677. E('button', {
  678. 'class': 'cbi-button cbi-button-add',
  679. 'title': _('Provide new network'),
  680. 'click': ui.createHandlerFn(this, 'handleAdd', inst)
  681. }, _('Add'))
  682. ];
  683. }
  684. else {
  685. var isDisabled = (inst.get('disabled') == '1' ||
  686. uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
  687. btns = [
  688. E('button', {
  689. 'class': 'cbi-button cbi-button-neutral enable-disable',
  690. 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
  691. 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
  692. }, isDisabled ? _('Enable') : _('Disable')),
  693. E('button', {
  694. 'class': 'cbi-button cbi-button-action important',
  695. 'title': _('Edit this network'),
  696. 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
  697. }, _('Edit')),
  698. E('button', {
  699. 'class': 'cbi-button cbi-button-negative remove',
  700. 'title': _('Delete this network'),
  701. 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
  702. }, _('Remove'))
  703. ];
  704. }
  705. return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
  706. };
  707. s.addModalOptions = function(s) {
  708. return network.getWifiNetwork(s.section).then(function(radioNet) {
  709. var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
  710. var o, ss;
  711. o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
  712. o.modalonly = true;
  713. ss = o.subsection;
  714. ss.tab('general', _('General Setup'));
  715. ss.tab('advanced', _('Advanced Settings'));
  716. var isDisabled = (radioNet.get('disabled') == '1' ||
  717. uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
  718. o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
  719. o.cfgvalue = L.bind(function(radioNet) {
  720. return render_modal_status(null, radioNet);
  721. }, this, radioNet);
  722. o.write = function() {};
  723. o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
  724. o.inputstyle = isDisabled ? 'apply' : 'reset';
  725. o.inputtitle = isDisabled ? _('Enable') : _('Disable');
  726. o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
  727. o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
  728. o.ucisection = s.section;
  729. if (hwtype == 'mac80211') {
  730. o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.'));
  731. o.wifiNetwork = radioNet;
  732. o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
  733. o.wifiNetwork = radioNet;
  734. o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
  735. o.default = o.enabled;
  736. o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
  737. o.datatype = 'range(0,114750)';
  738. o.placeholder = 'auto';
  739. o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
  740. o.datatype = 'min(256)';
  741. o.placeholder = _('off');
  742. o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
  743. o.datatype = 'uinteger';
  744. o.placeholder = _('off');
  745. o = ss.taboption('advanced', form.Flag, 'noscan', _('Force 40MHz mode'), _('Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!'));
  746. o.rmempty = true;
  747. o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
  748. o.datatype = 'range(15,65535)';
  749. o.placeholder = 100;
  750. o.rmempty = true;
  751. }
  752. o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
  753. o.modalonly = true;
  754. ss = o.subsection;
  755. ss.tab('general', _('General Setup'));
  756. ss.tab('encryption', _('Wireless Security'));
  757. ss.tab('macfilter', _('MAC-Filter'));
  758. ss.tab('advanced', _('Advanced Settings'));
  759. o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
  760. o.value('ap', _('Access Point'));
  761. o.value('sta', _('Client'));
  762. o.value('adhoc', _('Ad-Hoc'));
  763. o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
  764. o.depends('mode', 'mesh');
  765. o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
  766. o.rmempty = false;
  767. o.default = '1';
  768. o.depends('mode', 'mesh');
  769. o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
  770. o.rmempty = false;
  771. o.default = '0';
  772. o.datatype = 'range(-255,1)';
  773. o.depends('mode', 'mesh');
  774. o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
  775. o.datatype = 'maxlength(32)';
  776. o.depends('mode', 'ap');
  777. o.depends('mode', 'sta');
  778. o.depends('mode', 'adhoc');
  779. o.depends('mode', 'ahdemo');
  780. o.depends('mode', 'monitor');
  781. o.depends('mode', 'ap-wds');
  782. o.depends('mode', 'sta-wds');
  783. o.depends('mode', 'wds');
  784. o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
  785. o.datatype = 'macaddr';
  786. o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the <em>custom</em> field to define a new network.'));
  787. o.rmempty = true;
  788. o.multiple = true;
  789. o.novirtual = true;
  790. o.write = function(section_id, value) {
  791. return network.getDevice(section_id).then(L.bind(function(dev) {
  792. var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
  793. new_networks = {},
  794. values = L.toArray(value),
  795. tasks = [];
  796. for (var i = 0; i < values.length; i++) {
  797. new_networks[values[i]] = true;
  798. if (old_networks[values[i]])
  799. continue;
  800. tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
  801. return net || network.addNetwork(name, { proto: 'none' });
  802. }, this, values[i])).then(L.bind(function(dev, net) {
  803. if (net) {
  804. if (!net.isEmpty())
  805. net.set('type', 'bridge');
  806. net.addDevice(dev);
  807. }
  808. }, this, dev)));
  809. }
  810. for (var name in old_networks)
  811. if (!new_networks[name])
  812. tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
  813. if (net)
  814. net.deleteDevice(dev);
  815. }, this, dev)));
  816. return Promise.all(tasks);
  817. }, this));
  818. };
  819. if (hwtype == 'mac80211') {
  820. var mode = ss.children[0],
  821. bssid = ss.children[5],
  822. encr;
  823. mode.value('mesh', '802.11s');
  824. mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
  825. mode.value('monitor', _('Monitor'));
  826. bssid.depends('mode', 'adhoc');
  827. bssid.depends('mode', 'sta');
  828. bssid.depends('mode', 'sta-wds');
  829. o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
  830. o.depends('mode', 'ap');
  831. o.depends('mode', 'ap-wds');
  832. o.value('', _('disable'));
  833. o.value('allow', _('Allow listed only'));
  834. o.value('deny', _('Allow all except listed'));
  835. o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
  836. o.datatype = 'macaddr';
  837. o.depends('macfilter', 'allow');
  838. o.depends('macfilter', 'deny');
  839. o.load = function(section_id) {
  840. return network.getHostHints().then(L.bind(function(hints) {
  841. hints.getMACHints().map(L.bind(function(hint) {
  842. this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
  843. }, this));
  844. return form.DynamicList.prototype.load.apply(this, [section_id]);
  845. }, this));
  846. };
  847. mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
  848. mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
  849. mode.write = function(section_id, value) {
  850. switch (value) {
  851. case 'ap-wds':
  852. uci.set('wireless', section_id, 'mode', 'ap');
  853. uci.set('wireless', section_id, 'wds', '1');
  854. break;
  855. case 'sta-wds':
  856. uci.set('wireless', section_id, 'mode', 'sta');
  857. uci.set('wireless', section_id, 'wds', '1');
  858. break;
  859. default:
  860. uci.set('wireless', section_id, 'mode', value);
  861. uci.unset('wireless', section_id, 'wds');
  862. break;
  863. }
  864. };
  865. mode.cfgvalue = function(section_id) {
  866. var mode = uci.get('wireless', section_id, 'mode'),
  867. wds = uci.get('wireless', section_id, 'wds');
  868. if (mode == 'ap' && wds)
  869. return 'ap-wds';
  870. else if (mode == 'sta' && wds)
  871. return 'sta-wds';
  872. return mode;
  873. };
  874. o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
  875. o.depends('mode', 'ap');
  876. o.depends('mode', 'ap-wds');
  877. o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
  878. o.depends('mode', 'ap');
  879. o.depends('mode', 'ap-wds');
  880. o.default = o.enabled;
  881. o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
  882. o.depends('mode', 'ap');
  883. o.depends('mode', 'ap-wds');
  884. o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
  885. o.optional = true;
  886. o.placeholder = radioNet.getIfname();
  887. if (/^radio\d+\.network/.test(o.placeholder))
  888. o.placeholder = '';
  889. o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
  890. o.default = o.enabled;
  891. o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
  892. o.optional = true;
  893. o.placeholder = 2;
  894. o.datatype = 'range(1,255)';
  895. o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
  896. o.optional = true;
  897. o.placeholder = 600;
  898. o.datatype = 'uinteger';
  899. o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
  900. o.optional = true;
  901. o.datatype = 'uinteger';
  902. o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
  903. o.optional = true;
  904. o.placeholder = 300;
  905. o.datatype = 'uinteger';
  906. o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
  907. o.optional = true;
  908. o.placeholder = 65535;
  909. o.datatype = 'uinteger';
  910. o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
  911. o.default = o.enabled;
  912. }
  913. encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
  914. o.depends('mode', 'ap');
  915. o.depends('mode', 'sta');
  916. o.depends('mode', 'adhoc');
  917. o.depends('mode', 'ahdemo');
  918. o.depends('mode', 'ap-wds');
  919. o.depends('mode', 'sta-wds');
  920. o.depends('mode', 'mesh');
  921. o.cfgvalue = function(section_id) {
  922. var v = String(uci.get('wireless', section_id, 'encryption'));
  923. if (v == 'wep')
  924. return 'wep-open';
  925. else if (v.match(/\+/))
  926. return v.replace(/\+.+$/, '');
  927. return v;
  928. };
  929. o.write = function(section_id, value) {
  930. var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
  931. co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
  932. if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
  933. uci.unset('wireless', section_id, 'key');
  934. if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
  935. e += '+' + c;
  936. uci.set('wireless', section_id, 'encryption', e);
  937. };
  938. o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
  939. o.depends('encryption', 'wpa');
  940. o.depends('encryption', 'wpa2');
  941. o.depends('encryption', 'wpa3');
  942. o.depends('encryption', 'wpa3-mixed');
  943. o.depends('encryption', 'psk');
  944. o.depends('encryption', 'psk2');
  945. o.depends('encryption', 'wpa-mixed');
  946. o.depends('encryption', 'psk-mixed');
  947. o.value('auto', _('auto'));
  948. o.value('ccmp', _('Force CCMP (AES)'));
  949. o.value('tkip', _('Force TKIP'));
  950. o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
  951. o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
  952. o.cfgvalue = function(section_id) {
  953. var v = String(uci.get('wireless', section_id, 'encryption'));
  954. if (v.match(/\+/)) {
  955. v = v.replace(/^[^+]+\+/, '');
  956. if (v == 'aes')
  957. v = 'ccmp';
  958. else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
  959. v = 'tkip+ccmp';
  960. }
  961. return v;
  962. };
  963. var crypto_modes = [];
  964. if (hwtype == 'mac80211') {
  965. var has_supplicant = L.hasSystemFeature('wpasupplicant'),
  966. has_hostapd = L.hasSystemFeature('hostapd');
  967. // Probe EAP support
  968. var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
  969. has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
  970. // Probe SAE support
  971. var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
  972. has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
  973. // Probe OWE support
  974. var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
  975. has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
  976. // Probe Suite-B support
  977. var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
  978. has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
  979. if (has_hostapd || has_supplicant) {
  980. crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
  981. crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
  982. crypto_modes.push(['psk', 'WPA-PSK', 21]);
  983. }
  984. else {
  985. encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
  986. }
  987. if (has_ap_sae || has_sta_sae) {
  988. crypto_modes.push(['sae', 'WPA3-SAE', 31]);
  989. crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
  990. }
  991. if (has_ap_eap || has_sta_eap) {
  992. if (has_ap_eap192 || has_sta_eap192) {
  993. crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
  994. crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
  995. }
  996. crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
  997. crypto_modes.push(['wpa', 'WPA-EAP', 20]);
  998. }
  999. if (has_ap_owe || has_sta_owe) {
  1000. crypto_modes.push(['owe', 'OWE', 1]);
  1001. }
  1002. encr.crypto_support = {
  1003. 'ap': {
  1004. 'wep-open': true,
  1005. 'wep-shared': true,
  1006. 'psk': has_hostapd || _('Requires hostapd'),
  1007. 'psk2': has_hostapd || _('Requires hostapd'),
  1008. 'psk-mixed': has_hostapd || _('Requires hostapd'),
  1009. 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
  1010. 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
  1011. 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
  1012. 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
  1013. 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
  1014. 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
  1015. 'owe': has_ap_owe || _('Requires hostapd with OWE support')
  1016. },
  1017. 'sta': {
  1018. 'wep-open': true,
  1019. 'wep-shared': true,
  1020. 'psk': has_supplicant || _('Requires wpa-supplicant'),
  1021. 'psk2': has_supplicant || _('Requires wpa-supplicant'),
  1022. 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
  1023. 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
  1024. 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
  1025. 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
  1026. 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
  1027. 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
  1028. 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
  1029. 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
  1030. },
  1031. 'adhoc': {
  1032. 'wep-open': true,
  1033. 'wep-shared': true,
  1034. 'psk': has_supplicant || _('Requires wpa-supplicant'),
  1035. 'psk2': has_supplicant || _('Requires wpa-supplicant'),
  1036. 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
  1037. },
  1038. 'mesh': {
  1039. 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
  1040. },
  1041. 'ahdemo': {
  1042. 'wep-open': true,
  1043. 'wep-shared': true
  1044. },
  1045. 'wds': {
  1046. 'wep-open': true,
  1047. 'wep-shared': true
  1048. }
  1049. };
  1050. encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
  1051. encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
  1052. encr.validate = function(section_id, value) {
  1053. var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
  1054. modeval = modeopt.formvalue(section_id),
  1055. modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
  1056. enctitle = this.vallist[this.keylist.indexOf(value)];
  1057. if (value == 'none')
  1058. return true;
  1059. if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
  1060. return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
  1061. return this.crypto_support[modeval][value];
  1062. };
  1063. }
  1064. else if (hwtype == 'broadcom') {
  1065. crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
  1066. crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
  1067. crypto_modes.push(['psk', 'WPA-PSK', 21]);
  1068. }
  1069. crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
  1070. crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
  1071. crypto_modes.push(['none', _('No Encryption'), 0]);
  1072. crypto_modes.sort(function(a, b) { return b[2] - a[2] });
  1073. for (var i = 0; i < crypto_modes.length; i++) {
  1074. var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
  1075. : (crypto_modes[i][2] >= 20) ? _('medium security')
  1076. : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
  1077. encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
  1078. }
  1079. o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
  1080. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1081. o.rmempty = true;
  1082. o.datatype = 'host(0)';
  1083. o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
  1084. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1085. o.rmempty = true;
  1086. o.datatype = 'port';
  1087. o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
  1088. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1089. o.rmempty = true;
  1090. o.password = true;
  1091. o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
  1092. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1093. o.rmempty = true;
  1094. o.datatype = 'host(0)';
  1095. o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
  1096. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1097. o.rmempty = true;
  1098. o.datatype = 'port';
  1099. o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
  1100. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1101. o.rmempty = true;
  1102. o.password = true;
  1103. o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
  1104. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1105. o.rmempty = true;
  1106. o.datatype = 'host(0)';
  1107. o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
  1108. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1109. o.rmempty = true;
  1110. o.datatype = 'port';
  1111. o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
  1112. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1113. o.rmempty = true;
  1114. o.password = true;
  1115. o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
  1116. o.depends('encryption', 'psk');
  1117. o.depends('encryption', 'psk2');
  1118. o.depends('encryption', 'psk+psk2');
  1119. o.depends('encryption', 'psk-mixed');
  1120. o.depends('encryption', 'sae');
  1121. o.depends('encryption', 'sae-mixed');
  1122. o.datatype = 'wpakey';
  1123. o.rmempty = true;
  1124. o.password = true;
  1125. o.cfgvalue = function(section_id) {
  1126. var key = uci.get('wireless', section_id, 'key');
  1127. return /^[1234]$/.test(key) ? null : key;
  1128. };
  1129. o.write = function(section_id, value) {
  1130. uci.set('wireless', section_id, 'key', value);
  1131. uci.unset('wireless', section_id, 'key1');
  1132. uci.unset('wireless', section_id, 'key2');
  1133. uci.unset('wireless', section_id, 'key3');
  1134. uci.unset('wireless', section_id, 'key4');
  1135. };
  1136. o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
  1137. o.depends('encryption', 'wep-open');
  1138. o.depends('encryption', 'wep-shared');
  1139. o.value('1', _('Key #%d').format(1));
  1140. o.value('2', _('Key #%d').format(2));
  1141. o.value('3', _('Key #%d').format(3));
  1142. o.value('4', _('Key #%d').format(4));
  1143. o.cfgvalue = function(section_id) {
  1144. var slot = +uci.get('wireless', section_id, 'key');
  1145. return (slot >= 1 && slot <= 4) ? String(slot) : '';
  1146. };
  1147. o.write = function(section_id, value) {
  1148. uci.set('wireless', section_id, 'key', value);
  1149. };
  1150. for (var slot = 1; slot <= 4; slot++) {
  1151. o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
  1152. o.depends('encryption', 'wep-open');
  1153. o.depends('encryption', 'wep-shared');
  1154. o.datatype = 'wepkey';
  1155. o.rmempty = true;
  1156. o.password = true;
  1157. o.write = function(section_id, value) {
  1158. if (value != null && (value.length == 5 || value.length == 13))
  1159. value = 's:%s'.format(value);
  1160. uci.set('wireless', section_id, this.option, value);
  1161. };
  1162. }
  1163. if (hwtype == 'mac80211') {
  1164. // Probe 802.11r support (and EAP support as a proxy for Openwrt)
  1165. var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
  1166. o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
  1167. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1168. if (has_80211r)
  1169. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
  1170. o.rmempty = true;
  1171. o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
  1172. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1173. o.depends({ ieee80211r: '1' });
  1174. o.rmempty = true;
  1175. o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
  1176. o.depends({ ieee80211r: '1' });
  1177. o.placeholder = '4f57';
  1178. o.datatype = 'and(hexstring,length(4))';
  1179. o.rmempty = true;
  1180. o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
  1181. o.depends({ ieee80211r: '1' });
  1182. o.placeholder = '1000';
  1183. o.datatype = 'range(1000,65535)';
  1184. o.rmempty = true;
  1185. o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
  1186. o.depends({ ieee80211r: '1' });
  1187. o.value('1', _('FT over DS'));
  1188. o.value('0', _('FT over the Air'));
  1189. o.rmempty = true;
  1190. o = ss.taboption('encryption', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
  1191. o.depends({ ieee80211r: '1' });
  1192. o.default = o.enabled;
  1193. o.rmempty = false;
  1194. o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
  1195. o.depends({ ieee80211r: '1' });
  1196. o.placeholder = '10000';
  1197. o.datatype = 'uinteger';
  1198. o.rmempty = true;
  1199. o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
  1200. o.depends({ ieee80211r: '1' });
  1201. o.placeholder = '00004f577274';
  1202. o.datatype = 'and(hexstring,length(12))';
  1203. o.rmempty = true;
  1204. o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
  1205. o.depends({ ieee80211r: '1' });
  1206. o.placeholder = '0';
  1207. o.rmempty = true;
  1208. o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
  1209. o.depends({ ieee80211r: '1' });
  1210. o.rmempty = true;
  1211. o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
  1212. o.depends({ ieee80211r: '1' });
  1213. o.rmempty = true;
  1214. // End of 802.11r options
  1215. o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
  1216. o.value('tls', 'TLS');
  1217. o.value('ttls', 'TTLS');
  1218. o.value('peap', 'PEAP');
  1219. o.value('fast', 'FAST');
  1220. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1221. o = ss.taboption('encryption', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle,<br />requires the \"ca-bundle\" package"));
  1222. o.enabled = '1';
  1223. o.disabled = '0';
  1224. o.default = o.disabled;
  1225. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1226. o.validate = function(section_id, value) {
  1227. if (value == '1' && !L.hasSystemFeature('cabundle')) {
  1228. return _("This option cannot be used because the ca-bundle package is not installed.");
  1229. }
  1230. return true;
  1231. };
  1232. o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
  1233. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] });
  1234. o = ss.taboption('encryption', form.Value, 'subject_match', _('Certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com<br />See `logread -f` during handshake for actual values"));
  1235. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1236. o = ss.taboption('encryption', form.DynamicList, 'altsubject_match', _('Certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values<br />(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
  1237. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1238. o = ss.taboption('encryption', form.DynamicList, 'domain_match', _('Certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (exact match)"));
  1239. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1240. o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match', _('Certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
  1241. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1242. o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
  1243. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
  1244. o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
  1245. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
  1246. o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
  1247. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
  1248. o.password = true;
  1249. o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
  1250. o.value('PAP', 'PAP');
  1251. o.value('CHAP', 'CHAP');
  1252. o.value('MSCHAP', 'MSCHAP');
  1253. o.value('MSCHAPV2', 'MSCHAPv2');
  1254. o.value('EAP-GTC', 'EAP-GTC');
  1255. o.value('EAP-MD5', 'EAP-MD5');
  1256. o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
  1257. o.value('EAP-TLS', 'EAP-TLS');
  1258. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
  1259. o.validate = function(section_id, value) {
  1260. var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
  1261. ev = eo.formvalue(section_id);
  1262. if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
  1263. return _('This authentication type is not applicable to the selected EAP method.');
  1264. return true;
  1265. };
  1266. o = ss.taboption('encryption', form.Flag, 'ca_cert2_usesystem', _('Use system certificates for inner-tunnel'), _("Validate server certificate using built-in system CA bundle,<br />requires the \"ca-bundle\" package"));
  1267. o.enabled = '1';
  1268. o.disabled = '0';
  1269. o.default = o.disabled;
  1270. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1271. o.validate = function(section_id, value) {
  1272. if (value == '1' && !L.hasSystemFeature('cabundle')) {
  1273. return _("This option cannot be used because the ca-bundle package is not installed.");
  1274. }
  1275. return true;
  1276. };
  1277. o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
  1278. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
  1279. o = ss.taboption('encryption', form.Value, 'subject_match2', _('Inner certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com<br />See `logread -f` during handshake for actual values"));
  1280. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1281. o = ss.taboption('encryption', form.DynamicList, 'altsubject_match2', _('Inner certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values<br />(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
  1282. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1283. o = ss.taboption('encryption', form.DynamicList, 'domain_match2', _('Inner certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (exact match)"));
  1284. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1285. o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
  1286. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1287. o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
  1288. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1289. o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
  1290. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1291. o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
  1292. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
  1293. o.password = true;
  1294. o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
  1295. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
  1296. o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
  1297. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
  1298. o = ss.taboption('encryption', form.Value, 'password', _('Password'));
  1299. add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
  1300. o.password = true;
  1301. if (hwtype == 'mac80211') {
  1302. // ieee802.11w options
  1303. if (L.hasSystemFeature('hostapd', '11w')) {
  1304. o = ss.taboption('encryption', form.ListValue, 'ieee80211w', _('802.11w Management Frame Protection'), _("Requires the 'full' version of wpad/hostapd and support from the wifi driver <br />(as of Jan 2019: ath9k, ath10k, mwlwifi and mt76)"));
  1305. o.value('', _('Disabled'));
  1306. o.value('1', _('Optional'));
  1307. o.value('2', _('Required'));
  1308. add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1309. o.defaults = {
  1310. '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
  1311. '1': [{ encryption: 'sae-mixed'}],
  1312. '': []
  1313. };
  1314. o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
  1315. o.depends('ieee80211w', '1');
  1316. o.depends('ieee80211w', '2');
  1317. o.datatype = 'uinteger';
  1318. o.placeholder = '1000';
  1319. o.rmempty = true;
  1320. o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
  1321. o.depends('ieee80211w', '1');
  1322. o.depends('ieee80211w', '2');
  1323. o.datatype = 'uinteger';
  1324. o.placeholder = '201';
  1325. o.rmempty = true;
  1326. };
  1327. o = ss.taboption('encryption', form.Flag, 'wpa_disable_eapol_key_retries', _('Enable key reinstallation (KRACK) countermeasures'), _('Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.'));
  1328. add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
  1329. if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
  1330. o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
  1331. o.enabled = '1';
  1332. o.disabled = '0';
  1333. o.default = o.disabled;
  1334. o.depends('encryption', 'psk');
  1335. o.depends('encryption', 'psk2');
  1336. o.depends('encryption', 'psk-mixed');
  1337. o.depends('encryption', 'sae');
  1338. o.depends('encryption', 'sae-mixed');
  1339. }
  1340. }
  1341. }
  1342. });
  1343. };
  1344. s.handleRemove = function(section_id, ev) {
  1345. document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
  1346. return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
  1347. };
  1348. s.handleScan = function(radioDev, ev) {
  1349. var table = E('div', { 'class': 'table' }, [
  1350. E('div', { 'class': 'tr table-titles' }, [
  1351. E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
  1352. E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
  1353. E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
  1354. E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
  1355. E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
  1356. E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
  1357. E('div', { 'class': 'th cbi-section-actions right' }, ' '),
  1358. ])
  1359. ]);
  1360. var stop = E('button', {
  1361. 'class': 'btn',
  1362. 'click': L.bind(this.handleScanStartStop, this),
  1363. 'style': 'display:none',
  1364. 'data-state': 'stop'
  1365. }, _('Stop refresh'));
  1366. cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
  1367. var md = ui.showModal(_('Join Network: Wireless Scan'), [
  1368. table,
  1369. E('div', { 'class': 'right' }, [
  1370. stop,
  1371. ' ',
  1372. E('button', {
  1373. 'class': 'btn',
  1374. 'click': L.bind(this.handleScanAbort, this)
  1375. }, _('Dismiss'))
  1376. ])
  1377. ]);
  1378. md.style.maxWidth = '90%';
  1379. md.style.maxHeight = 'none';
  1380. this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
  1381. poll.add(this.pollFn);
  1382. poll.start();
  1383. };
  1384. s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
  1385. return radioDev.getScanList().then(L.bind(function(results) {
  1386. var rows = [];
  1387. for (var i = 0; i < results.length; i++)
  1388. scanCache[results[i].bssid] = results[i];
  1389. for (var k in scanCache)
  1390. if (scanCache[k].stale)
  1391. results.push(scanCache[k]);
  1392. results.sort(function(a, b) {
  1393. var diff = (b.quality - a.quality) || (a.channel - b.channel);
  1394. if (diff)
  1395. return diff;
  1396. if (a.ssid < b.ssid)
  1397. return -1;
  1398. else if (a.ssid > b.ssid)
  1399. return 1;
  1400. if (a.bssid < b.bssid)
  1401. return -1;
  1402. else if (a.bssid > b.bssid)
  1403. return 1;
  1404. });
  1405. for (var i = 0; i < results.length; i++) {
  1406. var res = results[i],
  1407. qv = res.quality || 0,
  1408. qm = res.quality_max || 0,
  1409. q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
  1410. s = res.stale ? 'opacity:0.5' : '';
  1411. rows.push([
  1412. E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
  1413. E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
  1414. E('span', { 'style': s }, '%d'.format(res.channel)),
  1415. E('span', { 'style': s }, '%h'.format(res.mode)),
  1416. E('span', { 'style': s }, '%h'.format(res.bssid)),
  1417. E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
  1418. E('div', { 'class': 'right' }, E('button', {
  1419. 'class': 'cbi-button cbi-button-action important',
  1420. 'click': L.bind(this.handleJoin, this, radioDev, res)
  1421. }, _('Join Network')))
  1422. ]);
  1423. res.stale = true;
  1424. }
  1425. cbi_update_table(table, rows);
  1426. stop.disabled = false;
  1427. stop.style.display = '';
  1428. stop.classList.remove('spinning');
  1429. }, this));
  1430. };
  1431. s.handleScanStartStop = function(ev) {
  1432. var btn = ev.currentTarget;
  1433. if (btn.getAttribute('data-state') == 'stop') {
  1434. poll.remove(this.pollFn);
  1435. btn.firstChild.data = _('Start refresh');
  1436. btn.setAttribute('data-state', 'start');
  1437. }
  1438. else {
  1439. poll.add(this.pollFn);
  1440. btn.firstChild.data = _('Stop refresh');
  1441. btn.setAttribute('data-state', 'stop');
  1442. btn.classList.add('spinning');
  1443. btn.disabled = true;
  1444. }
  1445. };
  1446. s.handleScanAbort = function(ev) {
  1447. var md = dom.parent(ev.target, 'div[aria-modal="true"]');
  1448. if (md) {
  1449. md.style.maxWidth = '';
  1450. md.style.maxHeight = '';
  1451. }
  1452. ui.hideModal();
  1453. poll.remove(this.pollFn);
  1454. this.pollFn = null;
  1455. };
  1456. s.handleJoinConfirm = function(radioDev, bss, form, ev) {
  1457. var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
  1458. passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
  1459. bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
  1460. zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
  1461. replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
  1462. nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
  1463. passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
  1464. bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
  1465. zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
  1466. enc = L.isObject(bss.encryption) ? bss.encryption : null,
  1467. is_wep = (enc && Array.isArray(enc.wep)),
  1468. is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
  1469. is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
  1470. if (nameval == null || (passopt && passval == null))
  1471. return;
  1472. var section_id = null;
  1473. return this.map.save(function() {
  1474. var wifi_sections = uci.sections('wireless', 'wifi-iface');
  1475. if (replopt.formvalue('_new_') == '1') {
  1476. for (var i = 0; i < wifi_sections.length; i++)
  1477. if (wifi_sections[i].device == radioDev.getName())
  1478. uci.remove('wireless', wifi_sections[i]['.name']);
  1479. }
  1480. if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
  1481. for (var i = 0; i < wifi_sections.length; i++)
  1482. if (wifi_sections[i].device == radioDev.getName())
  1483. uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
  1484. uci.unset('wireless', radioDev.getName(), 'disabled');
  1485. }
  1486. section_id = next_free_sid(wifi_sections.length);
  1487. uci.add('wireless', 'wifi-iface', section_id);
  1488. uci.set('wireless', section_id, 'device', radioDev.getName());
  1489. uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
  1490. uci.set('wireless', section_id, 'network', nameval);
  1491. if (bss.ssid != null) {
  1492. uci.set('wireless', section_id, 'ssid', bss.ssid);
  1493. if (bssidval == '1')
  1494. uci.set('wireless', section_id, 'bssid', bss.bssid);
  1495. }
  1496. else if (bss.bssid != null) {
  1497. uci.set('wireless', section_id, 'bssid', bss.bssid);
  1498. }
  1499. if (is_sae) {
  1500. uci.set('wireless', section_id, 'encryption', 'sae');
  1501. uci.set('wireless', section_id, 'key', passval);
  1502. }
  1503. else if (is_psk) {
  1504. for (var i = enc.wpa.length - 1; i >= 0; i--) {
  1505. if (enc.wpa[i] == 2) {
  1506. uci.set('wireless', section_id, 'encryption', 'psk2');
  1507. break;
  1508. }
  1509. else if (enc.wpa[i] == 1) {
  1510. uci.set('wireless', section_id, 'encryption', 'psk');
  1511. break;
  1512. }
  1513. }
  1514. uci.set('wireless', section_id, 'key', passval);
  1515. }
  1516. else if (is_wep) {
  1517. uci.set('wireless', section_id, 'encryption', 'wep-open');
  1518. uci.set('wireless', section_id, 'key', '1');
  1519. uci.set('wireless', section_id, 'key1', passval);
  1520. }
  1521. else {
  1522. uci.set('wireless', section_id, 'encryption', 'none');
  1523. }
  1524. return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
  1525. firewall.deleteNetwork(net.getName());
  1526. var zonePromise = zoneval
  1527. ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
  1528. : Promise.resolve();
  1529. return zonePromise.then(function(zone) {
  1530. if (zone)
  1531. zone.addNetwork(net.getName());
  1532. });
  1533. });
  1534. }).then(L.bind(function() {
  1535. return this.renderMoreOptionsModal(section_id);
  1536. }, this));
  1537. };
  1538. s.handleJoin = function(radioDev, bss, ev) {
  1539. this.handleScanAbort(ev);
  1540. var m2 = new form.Map('wireless'),
  1541. s2 = m2.section(form.NamedSection, '_new_'),
  1542. enc = L.isObject(bss.encryption) ? bss.encryption : null,
  1543. is_wep = (enc && Array.isArray(enc.wep)),
  1544. is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
  1545. replace, passphrase, name, bssid, zone;
  1546. var nameUsed = function(name) {
  1547. var s = uci.get('network', name);
  1548. if (s != null && s['.type'] != 'interface')
  1549. return true;
  1550. var net = (s != null) ? network.instantiateNetwork(name) : null;
  1551. return (net != null && !net.isEmpty());
  1552. };
  1553. s2.render = function() {
  1554. return Promise.all([
  1555. {},
  1556. this.renderUCISection('_new_')
  1557. ]).then(this.renderContents.bind(this));
  1558. };
  1559. replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
  1560. name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: <code>A-Z</code>, <code>a-z</code>, <code>0-9</code> and <code>_</code>'));
  1561. name.datatype = 'uciname';
  1562. name.default = 'wwan';
  1563. name.rmempty = false;
  1564. name.validate = function(section_id, value) {
  1565. if (nameUsed(value))
  1566. return _('The network name is already used');
  1567. return true;
  1568. };
  1569. for (var i = 2; nameUsed(name.default); i++)
  1570. name.default = 'wwan%d'.format(i);
  1571. if (is_wep || is_psk) {
  1572. passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
  1573. passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
  1574. passphrase.password = true;
  1575. passphrase.rmempty = false;
  1576. }
  1577. if (bss.ssid != null) {
  1578. bssid = s2.option(form.Flag, 'bssid', _('Lock to BSSID'), _('Instead of joining any network with a matching SSID, only connect to the BSSID <code>%h</code>.').format(bss.bssid));
  1579. bssid.default = '0';
  1580. }
  1581. zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>custom</em> field to define a new zone and attach the interface to it.'));
  1582. zone.default = 'wan';
  1583. return m2.render().then(L.bind(function(nodes) {
  1584. ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
  1585. nodes,
  1586. E('div', { 'class': 'right' }, [
  1587. E('button', {
  1588. 'class': 'btn',
  1589. 'click': ui.hideModal
  1590. }, _('Cancel')), ' ',
  1591. E('button', {
  1592. 'class': 'cbi-button cbi-button-positive important',
  1593. 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
  1594. }, _('Submit'))
  1595. ])
  1596. ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
  1597. }, this));
  1598. };
  1599. s.handleAdd = function(radioDev, ev) {
  1600. var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
  1601. uci.unset('wireless', radioDev.getName(), 'disabled');
  1602. uci.add('wireless', 'wifi-iface', section_id);
  1603. uci.set('wireless', section_id, 'device', radioDev.getName());
  1604. uci.set('wireless', section_id, 'mode', 'ap');
  1605. uci.set('wireless', section_id, 'ssid', 'OpenWrt');
  1606. uci.set('wireless', section_id, 'encryption', 'none');
  1607. this.addedSection = section_id;
  1608. return this.renderMoreOptionsModal(section_id);
  1609. };
  1610. o = s.option(form.DummyValue, '_badge');
  1611. o.modalonly = false;
  1612. o.textvalue = function(section_id) {
  1613. var inst = this.section.lookupRadioOrNetwork(section_id),
  1614. node = E('div', { 'class': 'center' });
  1615. if (inst.getWifiNetworks)
  1616. node.appendChild(render_radio_badge(inst));
  1617. else
  1618. node.appendChild(render_network_badge(inst));
  1619. return node;
  1620. };
  1621. o = s.option(form.DummyValue, '_stat');
  1622. o.modalonly = false;
  1623. o.textvalue = function(section_id) {
  1624. var inst = this.section.lookupRadioOrNetwork(section_id);
  1625. if (inst.getWifiNetworks)
  1626. return render_radio_status(inst, this.section.wifis.filter(function(e) {
  1627. return (e.getWifiDeviceName() == inst.getName());
  1628. }));
  1629. else
  1630. return render_network_status(inst);
  1631. };
  1632. return m.render().then(L.bind(function(m, nodes) {
  1633. poll.add(L.bind(function() {
  1634. var section_ids = m.children[0].cfgsections(),
  1635. tasks = [ network.getHostHints(), network.getWifiDevices() ];
  1636. for (var i = 0; i < section_ids.length; i++) {
  1637. var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
  1638. dsc = row.querySelector('[data-name="_stat"] > div'),
  1639. btns = row.querySelectorAll('.cbi-section-actions button');
  1640. if (dsc.getAttribute('restart') == '') {
  1641. dsc.setAttribute('restart', '1');
  1642. tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
  1643. ui.addNotification(null, E('p', e.message));
  1644. }));
  1645. }
  1646. else if (dsc.getAttribute('restart') == '1') {
  1647. dsc.removeAttribute('restart');
  1648. btns[0].classList.remove('spinning');
  1649. btns[0].disabled = false;
  1650. }
  1651. }
  1652. return Promise.all(tasks)
  1653. .then(L.bind(function(hosts_radios) {
  1654. var tasks = [];
  1655. for (var i = 0; i < hosts_radios[1].length; i++)
  1656. tasks.push(hosts_radios[1][i].getWifiNetworks());
  1657. return Promise.all(tasks).then(function(data) {
  1658. hosts_radios[2] = [];
  1659. for (var i = 0; i < data.length; i++)
  1660. hosts_radios[2].push.apply(hosts_radios[2], data[i]);
  1661. return hosts_radios;
  1662. });
  1663. }, network))
  1664. .then(L.bind(function(hosts_radios_wifis) {
  1665. var tasks = [];
  1666. for (var i = 0; i < hosts_radios_wifis[2].length; i++)
  1667. tasks.push(hosts_radios_wifis[2][i].getAssocList());
  1668. return Promise.all(tasks).then(function(data) {
  1669. hosts_radios_wifis[3] = [];
  1670. for (var i = 0; i < data.length; i++) {
  1671. var wifiNetwork = hosts_radios_wifis[2][i],
  1672. radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
  1673. for (var j = 0; j < data[i].length; j++)
  1674. hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
  1675. }
  1676. return hosts_radios_wifis;
  1677. });
  1678. }, network))
  1679. .then(L.bind(this.poll_status, this, nodes));
  1680. }, this), 5);
  1681. var table = E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
  1682. E('div', { 'class': 'tr table-titles' }, [
  1683. E('div', { 'class': 'th nowrap' }, _('Network')),
  1684. E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
  1685. E('div', { 'class': 'th' }, _('Host')),
  1686. E('div', { 'class': 'th' }, _('Signal / Noise')),
  1687. E('div', { 'class': 'th' }, _('RX Rate / TX Rate'))
  1688. ])
  1689. ]);
  1690. cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
  1691. return E([ nodes, E('h3', _('Associated Stations')), table ]);
  1692. }, this, m));
  1693. }
  1694. });