interfaces.js 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271
  1. 'use strict';
  2. 'require view';
  3. 'require dom';
  4. 'require poll';
  5. 'require fs';
  6. 'require ui';
  7. 'require uci';
  8. 'require form';
  9. 'require network';
  10. 'require firewall';
  11. 'require tools.widgets as widgets';
  12. 'require tools.network as nettools';
  13. var isReadonlyView = !L.hasViewPermission() || null;
  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.network))
  19. for (var i = 0; i < changes.network.length; i++)
  20. n += (changes.network[i][1] == section_id);
  21. if (Array.isArray(changes.dhcp))
  22. for (var i = 0; i < changes.dhcp.length; i++)
  23. n += (changes.dhcp[i][1] == section_id);
  24. return n;
  25. }
  26. function render_iface(dev, alias) {
  27. var type = dev ? dev.getType() : 'ethernet',
  28. up = dev ? dev.isUp() : false;
  29. return E('span', { class: 'cbi-tooltip-container' }, [
  30. E('img', { 'class' : 'middle', 'src': L.resource('icons/%s%s.png').format(
  31. alias ? 'alias' : type,
  32. up ? '' : '_disabled') }),
  33. E('span', { 'class': 'cbi-tooltip ifacebadge large' }, [
  34. E('img', { 'src': L.resource('icons/%s%s.png').format(
  35. type, up ? '' : '_disabled') }),
  36. L.itemlist(E('span', { 'class': 'left' }), [
  37. _('Type'), dev ? dev.getTypeI18n() : null,
  38. _('Device'), dev ? dev.getName() : _('Not present'),
  39. _('Connected'), up ? _('yes') : _('no'),
  40. _('MAC'), dev ? dev.getMAC() : null,
  41. _('RX'), dev ? '%.2mB (%d %s)'.format(dev.getRXBytes(), dev.getRXPackets(), _('Pkts.')) : null,
  42. _('TX'), dev ? '%.2mB (%d %s)'.format(dev.getTXBytes(), dev.getTXPackets(), _('Pkts.')) : null
  43. ])
  44. ])
  45. ]);
  46. }
  47. function render_status(node, ifc, with_device) {
  48. var desc = null, c = [];
  49. if (ifc.isDynamic())
  50. desc = _('Virtual dynamic interface');
  51. else if (ifc.isAlias())
  52. desc = _('Alias Interface');
  53. else if (!uci.get('network', ifc.getName()))
  54. return L.itemlist(node, [
  55. null, E('em', _('Interface is marked for deletion'))
  56. ]);
  57. var i18n = ifc.getI18n();
  58. if (i18n)
  59. desc = desc ? '%s (%s)'.format(desc, i18n) : i18n;
  60. var changecount = with_device ? 0 : count_changes(ifc.getName()),
  61. ipaddrs = changecount ? [] : ifc.getIPAddrs(),
  62. ip6addrs = changecount ? [] : ifc.getIP6Addrs(),
  63. errors = ifc.getErrors(),
  64. maindev = ifc.getL3Device() || ifc.getDevice(),
  65. macaddr = maindev ? maindev.getMAC() : null;
  66. return L.itemlist(node, [
  67. _('Protocol'), with_device ? null : (desc || '?'),
  68. _('Device'), with_device ? (maindev ? maindev.getShortName() : E('em', _('Not present'))) : null,
  69. _('Uptime'), (!changecount && ifc.isUp()) ? '%t'.format(ifc.getUptime()) : null,
  70. _('MAC'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && macaddr) ? macaddr : null,
  71. _('RX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getRXBytes(), maindev.getRXPackets(), _('Pkts.')) : null,
  72. _('TX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getTXBytes(), maindev.getTXPackets(), _('Pkts.')) : null,
  73. _('IPv4'), ipaddrs[0],
  74. _('IPv4'), ipaddrs[1],
  75. _('IPv4'), ipaddrs[2],
  76. _('IPv4'), ipaddrs[3],
  77. _('IPv4'), ipaddrs[4],
  78. _('IPv6'), ip6addrs[0],
  79. _('IPv6'), ip6addrs[1],
  80. _('IPv6'), ip6addrs[2],
  81. _('IPv6'), ip6addrs[3],
  82. _('IPv6'), ip6addrs[4],
  83. _('IPv6'), ip6addrs[5],
  84. _('IPv6'), ip6addrs[6],
  85. _('IPv6'), ip6addrs[7],
  86. _('IPv6'), ip6addrs[8],
  87. _('IPv6'), ip6addrs[9],
  88. _('IPv6-PD'), changecount ? null : ifc.getIP6Prefix(),
  89. _('Information'), with_device ? null : (ifc.get('auto') != '0' ? null : _('Not started on boot')),
  90. _('Error'), errors ? errors[0] : null,
  91. _('Error'), errors ? errors[1] : null,
  92. _('Error'), errors ? errors[2] : null,
  93. _('Error'), errors ? errors[3] : null,
  94. _('Error'), errors ? errors[4] : null,
  95. null, changecount ? E('a', {
  96. href: '#',
  97. click: L.bind(ui.changes.displayChanges, ui.changes)
  98. }, _('Interface has %d pending changes').format(changecount)) : null
  99. ]);
  100. }
  101. function render_modal_status(node, ifc) {
  102. var dev = ifc ? (ifc.getDevice() || ifc.getL3Device() || ifc.getL3Device()) : null;
  103. dom.content(node, [
  104. E('img', {
  105. 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
  106. 'title': dev ? dev.getTypeI18n() : _('Not present')
  107. }),
  108. ifc ? render_status(E('span'), ifc, true) : E('em', _('Interface not present or not connected yet.'))
  109. ]);
  110. return node;
  111. }
  112. function render_ifacebox_status(node, ifc) {
  113. var dev = ifc.getL3Device() || ifc.getDevice(),
  114. subdevs = ifc.getDevices(),
  115. c = [ render_iface(dev, ifc.isAlias()) ];
  116. if (subdevs && subdevs.length) {
  117. var sifs = [ ' (' ];
  118. for (var j = 0; j < subdevs.length; j++)
  119. sifs.push(render_iface(subdevs[j]));
  120. sifs.push(')');
  121. c.push(E('span', {}, sifs));
  122. }
  123. c.push(E('br'));
  124. c.push(E('small', {}, ifc.isAlias() ? _('Alias of "%s"').format(ifc.isAlias())
  125. : (dev ? dev.getName() : E('em', _('Not present')))));
  126. dom.content(node, c);
  127. return firewall.getZoneByNetwork(ifc.getName()).then(L.bind(function(zone) {
  128. this.style.backgroundColor = zone ? zone.getColor() : '#EEEEEE';
  129. this.title = zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned');
  130. }, node.previousElementSibling));
  131. }
  132. function iface_updown(up, id, ev, force) {
  133. var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
  134. dsc = row.querySelector('[data-name="_ifacestat"] > div'),
  135. btns = row.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down');
  136. btns[+!up].blur();
  137. btns[+!up].classList.add('spinning');
  138. btns[0].disabled = true;
  139. btns[1].disabled = true;
  140. if (!up) {
  141. L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) {
  142. var info = null; try { info = JSON.parse(res); } catch(e) {}
  143. if (L.isObject(info) &&
  144. Array.isArray(info.inbound_interfaces) &&
  145. info.inbound_interfaces.filter(function(i) { return i == id })[0]) {
  146. ui.showModal(_('Confirm disconnect'), [
  147. E('p', _('You appear to be currently connected to the device via the "%h" interface. Do you really want to shut down the interface?').format(id)),
  148. E('div', { 'class': 'right' }, [
  149. E('button', {
  150. 'class': 'cbi-button cbi-button-neutral',
  151. 'click': function(ev) {
  152. btns[1].classList.remove('spinning');
  153. btns[1].disabled = false;
  154. btns[0].disabled = false;
  155. ui.hideModal();
  156. }
  157. }, _('Cancel')),
  158. ' ',
  159. E('button', {
  160. 'class': 'cbi-button cbi-button-negative important',
  161. 'click': function(ev) {
  162. dsc.setAttribute('disconnect', '');
  163. dom.content(dsc, E('em', _('Interface is shutting down...')));
  164. ui.hideModal();
  165. }
  166. }, _('Disconnect'))
  167. ])
  168. ]);
  169. }
  170. else {
  171. dsc.setAttribute('disconnect', '');
  172. dom.content(dsc, E('em', _('Interface is shutting down...')));
  173. }
  174. });
  175. }
  176. else {
  177. dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : '');
  178. dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...')));
  179. }
  180. }
  181. function get_netmask(s, use_cfgvalue) {
  182. var readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue',
  183. addrs = L.toArray(s[readfn](s.section, 'ipaddr')),
  184. mask = s[readfn](s.section, 'netmask'),
  185. firstsubnet = mask ? addrs[0] + '/' + mask : addrs.filter(function(a) { return a.indexOf('/') > 0 })[0];
  186. if (firstsubnet == null)
  187. return null;
  188. var subnetmask = firstsubnet.split('/')[1];
  189. if (!isNaN(subnetmask))
  190. subnetmask = network.prefixToMask(+subnetmask);
  191. return subnetmask;
  192. }
  193. return view.extend({
  194. poll_status: function(map, networks) {
  195. var resolveZone = null;
  196. for (var i = 0; i < networks.length; i++) {
  197. var ifc = networks[i],
  198. row = map.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc.getName()));
  199. if (row == null)
  200. continue;
  201. var dsc = row.querySelector('[data-name="_ifacestat"] > div'),
  202. box = row.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
  203. btn1 = row.querySelector('.cbi-section-actions .reconnect'),
  204. btn2 = row.querySelector('.cbi-section-actions .down'),
  205. stat = document.querySelector('[id="%s-ifc-status"]'.format(ifc.getName())),
  206. resolveZone = render_ifacebox_status(box, ifc),
  207. disabled = ifc ? !ifc.isUp() : true,
  208. dynamic = ifc ? ifc.isDynamic() : false;
  209. if (dsc.hasAttribute('reconnect')) {
  210. dom.content(dsc, E('em', _('Interface is starting...')));
  211. }
  212. else if (dsc.hasAttribute('disconnect')) {
  213. dom.content(dsc, E('em', _('Interface is stopping...')));
  214. }
  215. else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) {
  216. render_status(dsc, ifc, false);
  217. }
  218. else if (!ifc.getProtocol()) {
  219. var e = map.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc.getName()));
  220. if (e) e.disabled = true;
  221. var link = L.url('admin/system/opkg') + '?query=luci-proto';
  222. dom.content(dsc, [
  223. E('em', _('Unsupported protocol type.')), E('br'),
  224. E('a', { href: link }, _('Install protocol extensions...'))
  225. ]);
  226. }
  227. else {
  228. dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
  229. }
  230. if (stat) {
  231. var dev = ifc.getDevice();
  232. dom.content(stat, [
  233. E('img', {
  234. 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
  235. 'title': dev ? dev.getTypeI18n() : _('Not present')
  236. }),
  237. render_status(E('span'), ifc, true)
  238. ]);
  239. }
  240. btn1.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic;
  241. btn2.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled;
  242. }
  243. return Promise.all([ resolveZone, network.flushCache() ]);
  244. },
  245. load: function() {
  246. return Promise.all([
  247. network.getDSLModemType(),
  248. network.getDevices(),
  249. fs.lines('/etc/iproute2/rt_tables'),
  250. uci.changes()
  251. ]);
  252. },
  253. render: function(data) {
  254. var dslModemType = data[0],
  255. netDevs = data[1],
  256. m, s, o;
  257. var rtTables = data[2].map(function(l) {
  258. var m = l.trim().match(/^(\d+)\s+(\S+)$/);
  259. return m ? [ +m[1], m[2] ] : null;
  260. }).filter(function(e) {
  261. return e && e[0] > 0;
  262. });
  263. m = new form.Map('network');
  264. m.tabbed = true;
  265. m.chain('dhcp');
  266. s = m.section(form.GridSection, 'interface', _('Interfaces'));
  267. s.anonymous = true;
  268. s.addremove = true;
  269. s.addbtntitle = _('Add new interface...');
  270. s.load = function() {
  271. return Promise.all([
  272. network.getNetworks(),
  273. firewall.getZones()
  274. ]).then(L.bind(function(data) {
  275. this.networks = data[0];
  276. this.zones = data[1];
  277. }, this));
  278. };
  279. s.tab('general', _('General Settings'));
  280. s.tab('advanced', _('Advanced Settings'));
  281. s.tab('physical', _('Physical Settings'));
  282. s.tab('brport', _('Bridge port specific options'));
  283. s.tab('bridgevlan', _('Bridge VLAN filtering'));
  284. s.tab('firewall', _('Firewall Settings'));
  285. s.tab('dhcp', _('DHCP Server'));
  286. s.cfgsections = function() {
  287. return this.networks.map(function(n) { return n.getName() })
  288. .filter(function(n) { return n != 'loopback' });
  289. };
  290. s.modaltitle = function(section_id) {
  291. return _('Interfaces') + ' » ' + section_id.toUpperCase();
  292. };
  293. s.renderRowActions = function(section_id) {
  294. var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
  295. net = this.networks.filter(function(n) { return n.getName() == section_id })[0],
  296. disabled = net ? !net.isUp() : true,
  297. dynamic = net ? net.isDynamic() : false;
  298. dom.content(tdEl.lastChild, [
  299. E('button', {
  300. 'class': 'cbi-button cbi-button-neutral reconnect',
  301. 'click': iface_updown.bind(this, true, section_id),
  302. 'title': _('Reconnect this interface'),
  303. 'disabled': dynamic ? 'disabled' : null
  304. }, _('Restart')),
  305. E('button', {
  306. 'class': 'cbi-button cbi-button-neutral down',
  307. 'click': iface_updown.bind(this, false, section_id),
  308. 'title': _('Shutdown this interface'),
  309. 'disabled': (dynamic || disabled) ? 'disabled' : null
  310. }, _('Stop')),
  311. tdEl.lastChild.firstChild,
  312. tdEl.lastChild.lastChild
  313. ]);
  314. if (!dynamic && net && !uci.get('network', net.getName())) {
  315. tdEl.lastChild.childNodes[0].disabled = true;
  316. tdEl.lastChild.childNodes[2].disabled = true;
  317. tdEl.lastChild.childNodes[3].disabled = true;
  318. }
  319. return tdEl;
  320. };
  321. s.addModalOptions = function(s) {
  322. var protoval = uci.get('network', s.section, 'proto'),
  323. protoclass = protoval ? network.getProtocol(protoval) : null,
  324. o, ifname_single, ifname_multi, proto_select, proto_switch, type, stp, igmp, ss, so;
  325. if (!protoval)
  326. return;
  327. return network.getNetwork(s.section).then(L.bind(function(ifc) {
  328. var protocols = network.getProtocols();
  329. protocols.sort(function(a, b) {
  330. return a.getProtocol() > b.getProtocol();
  331. });
  332. o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
  333. o.modalonly = true;
  334. o.cfgvalue = L.bind(function(section_id) {
  335. var net = this.networks.filter(function(n) { return n.getName() == section_id })[0];
  336. return render_modal_status(E('div', {
  337. 'id': '%s-ifc-status'.format(section_id),
  338. 'class': 'ifacebadge large'
  339. }), net);
  340. }, this);
  341. o.write = function() {};
  342. proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol'));
  343. proto_select.modalonly = true;
  344. proto_switch = s.taboption('general', form.Button, '_switch_proto');
  345. proto_switch.modalonly = true;
  346. proto_switch.title = _('Really switch protocol?');
  347. proto_switch.inputtitle = _('Switch protocol');
  348. proto_switch.inputstyle = 'apply';
  349. proto_switch.onclick = L.bind(function(ev) {
  350. s.map.save()
  351. .then(L.bind(m.load, m))
  352. .then(L.bind(m.render, m))
  353. .then(L.bind(this.renderMoreOptionsModal, this, s.section));
  354. }, this);
  355. o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot'));
  356. o.modalonly = true;
  357. o.default = o.enabled;
  358. if (L.hasSystemFeature('firewall')) {
  359. o = s.taboption('firewall', 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.'));
  360. o.network = ifc.getName();
  361. o.optional = true;
  362. o.cfgvalue = function(section_id) {
  363. return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) {
  364. return (zone != null ? zone.getName() : null);
  365. });
  366. };
  367. o.write = o.remove = function(section_id, value) {
  368. return Promise.all([
  369. firewall.getZoneByNetwork(ifc.getName()),
  370. (value != null) ? firewall.getZone(value) : null
  371. ]).then(function(data) {
  372. var old_zone = data[0],
  373. new_zone = data[1];
  374. if (old_zone == null && new_zone == null && (value == null || value == ''))
  375. return;
  376. if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName())
  377. return;
  378. if (old_zone != null)
  379. old_zone.deleteNetwork(ifc.getName());
  380. if (new_zone != null)
  381. new_zone.addNetwork(ifc.getName());
  382. else if (value != null)
  383. return firewall.addZone(value).then(function(new_zone) {
  384. new_zone.addNetwork(ifc.getName());
  385. });
  386. });
  387. };
  388. }
  389. for (var i = 0; i < protocols.length; i++) {
  390. proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
  391. if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
  392. proto_switch.depends('proto', protocols[i].getProtocol());
  393. }
  394. if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) {
  395. o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp');
  396. o.depends('proto', 'static');
  397. ss = o.subsection;
  398. ss.uciconfig = 'dhcp';
  399. ss.addremove = false;
  400. ss.anonymous = true;
  401. ss.tab('general', _('General Setup'));
  402. ss.tab('advanced', _('Advanced Settings'));
  403. ss.tab('ipv6', _('IPv6 Settings'));
  404. ss.filter = function(section_id) {
  405. return (uci.get('dhcp', section_id, 'interface') == ifc.getName());
  406. };
  407. ss.renderSectionPlaceholder = function() {
  408. return E('div', { 'class': 'cbi-section-create' }, [
  409. E('p', _('No DHCP Server configured for this interface') + ' &#160; '),
  410. E('button', {
  411. 'class': 'cbi-button cbi-button-add',
  412. 'title': _('Setup DHCP Server'),
  413. 'click': ui.createHandlerFn(this, function(section_id, ev) {
  414. this.map.save(function() {
  415. uci.add('dhcp', 'dhcp', section_id);
  416. uci.set('dhcp', section_id, 'interface', section_id);
  417. uci.set('dhcp', section_id, 'start', 100);
  418. uci.set('dhcp', section_id, 'limit', 150);
  419. uci.set('dhcp', section_id, 'leasetime', '12h');
  420. });
  421. }, ifc.getName())
  422. }, _('Setup DHCP Server'))
  423. ]);
  424. };
  425. ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
  426. so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
  427. so.optional = true;
  428. so.datatype = 'or(uinteger,ip4addr("nomask"))';
  429. so.default = '100';
  430. so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
  431. so.optional = true;
  432. so.datatype = 'uinteger';
  433. so.default = '150';
  434. so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
  435. so.optional = true;
  436. so.default = '12h';
  437. so = ss.taboption('advanced', form.Flag, 'dynamicdhcp', _('Dynamic <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>'), _('Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.'));
  438. so.default = so.enabled;
  439. ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
  440. // XXX: is this actually useful?
  441. //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
  442. so = ss.taboption('advanced', form.Value, 'netmask', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Netmask'), _('Override the netmask sent to clients. Normally it is calculated from the subnet that is served.'));
  443. so.optional = true;
  444. so.datatype = 'ip4addr';
  445. so.render = function(option_index, section_id, in_table) {
  446. this.placeholder = get_netmask(s, true);
  447. return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
  448. };
  449. so.validate = function(section_id, value) {
  450. var uielem = this.getUIElement(section_id);
  451. if (uielem)
  452. uielem.setPlaceholder(get_netmask(s, false));
  453. return form.Value.prototype.validate.apply(this, [ section_id, value ]);
  454. };
  455. ss.taboption('advanced', form.DynamicList, 'dhcp_option', _('DHCP-Options'), _('Define additional DHCP options, for example "<code>6,192.168.2.1,192.168.2.2</code>" which advertises different DNS servers to clients.'));
  456. for (var i = 0; i < ss.children.length; i++)
  457. if (ss.children[i].option != 'ignore')
  458. ss.children[i].depends('ignore', '0');
  459. so = ss.taboption('ipv6', form.ListValue, 'ra', _('Router Advertisement-Service'));
  460. so.value('', _('disabled'));
  461. so.value('server', _('server mode'));
  462. so.value('relay', _('relay mode'));
  463. so.value('hybrid', _('hybrid mode'));
  464. so = ss.taboption('ipv6', form.ListValue, 'dhcpv6', _('DHCPv6-Service'));
  465. so.value('', _('disabled'));
  466. so.value('server', _('server mode'));
  467. so.value('relay', _('relay mode'));
  468. so.value('hybrid', _('hybrid mode'));
  469. so = ss.taboption('ipv6', form.ListValue, 'ndp', _('NDP-Proxy'));
  470. so.value('', _('disabled'));
  471. so.value('relay', _('relay mode'));
  472. so.value('hybrid', _('hybrid mode'));
  473. so = ss.taboption('ipv6', form.Flag , 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.'));
  474. so.depends('dhcpv6', 'relay');
  475. so.depends('dhcpv6', 'hybrid');
  476. so = ss.taboption('ipv6', form.ListValue, 'ra_management', _('DHCPv6-Mode'), _('Default is stateless + stateful'));
  477. so.value('0', _('stateless'));
  478. so.value('1', _('stateless + stateful'));
  479. so.value('2', _('stateful-only'));
  480. so.depends('dhcpv6', 'server');
  481. so.depends('dhcpv6', 'hybrid');
  482. so.default = '1';
  483. so = ss.taboption('ipv6', form.Flag, 'ra_default', _('Always announce default router'), _('Announce as default router even if no public prefix is available.'));
  484. so.depends('ra', 'server');
  485. so.depends('ra', 'hybrid');
  486. ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced DNS servers'));
  487. ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'));
  488. }
  489. ifc.renderFormOptions(s);
  490. nettools.addDeviceOptions(s, null, true);
  491. // Common interface options
  492. o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
  493. o.default = o.enabled;
  494. o = nettools.replaceOption(s, 'advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
  495. o.default = o.enabled;
  496. o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
  497. o.depends('peerdns', '0');
  498. o.datatype = 'ipaddr';
  499. o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns_search', _('DNS search domains'));
  500. o.depends('peerdns', '0');
  501. o.datatype = 'hostname';
  502. o = nettools.replaceOption(s, 'advanced', form.Value, 'dns_metric', _('DNS weight'), _('The DNS server entries in the local resolv.conf are primarily sorted by the weight specified here'));
  503. o.datatype = 'uinteger';
  504. o.placeholder = '0';
  505. o = nettools.replaceOption(s, 'advanced', form.Value, 'metric', _('Use gateway metric'));
  506. o.datatype = 'uinteger';
  507. o.placeholder = '0';
  508. o = nettools.replaceOption(s, 'advanced', form.Value, 'ip4table', _('Override IPv4 routing table'));
  509. o.datatype = 'or(uinteger, string)';
  510. for (var i = 0; i < rtTables.length; i++)
  511. o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
  512. o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6table', _('Override IPv6 routing table'));
  513. o.datatype = 'or(uinteger, string)';
  514. for (var i = 0; i < rtTables.length; i++)
  515. o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][0], rtTables[i][1]));
  516. o = nettools.replaceOption(s, 'advanced', form.Flag, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
  517. o.default = o.enabled;
  518. o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6assign', _('IPv6 assignment length'), _('Assign a part of given length of every public IPv6-prefix to this interface'));
  519. o.value('', _('disabled'));
  520. o.value('64');
  521. o.datatype = 'max(128)';
  522. o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
  523. o.placeholder = '0';
  524. o.validate = function(section_id, value) {
  525. if (value == null || value == '')
  526. return true;
  527. var n = parseInt(value, 16);
  528. if (!/^(0x)?[0-9a-fA-F]+$/.test(value) || isNaN(n) || n >= 0xffffffff)
  529. return _('Expecting a hexadecimal assignment hint');
  530. return true;
  531. };
  532. for (var i = 33; i <= 64; i++)
  533. o.depends('ip6assign', String(i));
  534. o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
  535. o.value('local', 'local (%s)'.format(_('Local ULA')));
  536. var prefixClasses = {};
  537. this.networks.forEach(function(net) {
  538. var prefixes = net._ubus('ipv6-prefix');
  539. if (Array.isArray(prefixes)) {
  540. prefixes.forEach(function(pfx) {
  541. if (L.isObject(pfx) && typeof(pfx['class']) == 'string') {
  542. prefixClasses[pfx['class']] = prefixClasses[pfx['class']] || {};
  543. prefixClasses[pfx['class']][net.getName()] = true;
  544. }
  545. });
  546. }
  547. });
  548. Object.keys(prefixClasses).sort().forEach(function(c) {
  549. var networks = Object.keys(prefixClasses[c]).sort().join(', ');
  550. o.value(c, (c != networks) ? '%s (%s)'.format(c, networks) : c);
  551. });
  552. o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6ifaceid', _('IPv6 suffix'), _("Optional. Allowed values: 'eui64', 'random', fixed value like '::1' or '::1:2'. When IPv6 prefix (like 'a:b:c:d::') is received from a delegating server, use the suffix (like '::1') to form the IPv6 address ('a:b:c:d::1') for the interface."));
  553. o.datatype = 'ip6hostid';
  554. o.placeholder = '::1';
  555. o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6weight', _('IPv6 preference'), _('When delegating prefixes to multiple downstreams, interfaces with a higher preference value are considered first when allocating subnets.'));
  556. o.datatype = 'uinteger';
  557. o.placeholder = '0';
  558. for (var i = 0; i < s.children.length; i++) {
  559. o = s.children[i];
  560. switch (o.option) {
  561. case 'proto':
  562. case 'auto':
  563. case '_dhcp':
  564. case '_zone':
  565. case '_switch_proto':
  566. case '_ifacestat_modal':
  567. continue;
  568. case 'ifname_multi':
  569. case 'ifname_single':
  570. case 'igmp_snooping':
  571. case 'stp':
  572. case 'type':
  573. var deps = [];
  574. for (var j = 0; j < protocols.length; j++) {
  575. if (!protocols[j].isVirtual()) {
  576. if (o.deps.length)
  577. for (var k = 0; k < o.deps.length; k++)
  578. deps.push(Object.assign({ proto: protocols[j].getProtocol() }, o.deps[k]));
  579. else
  580. deps.push({ proto: protocols[j].getProtocol() });
  581. }
  582. }
  583. o.deps = deps;
  584. break;
  585. default:
  586. if (o.deps.length)
  587. for (var j = 0; j < o.deps.length; j++)
  588. o.deps[j].proto = protoval;
  589. else
  590. o.depends('proto', protoval);
  591. }
  592. }
  593. this.activeSection = s.section;
  594. }, this));
  595. };
  596. s.handleModalCancel = function(/* ... */) {
  597. var type = uci.get('network', this.activeSection || this.addedSection, 'type'),
  598. ifname = (type == 'bridge') ? 'br-%s'.format(this.activeSection || this.addedSection) : null;
  599. uci.sections('network', 'bridge-vlan', function(bvs) {
  600. if (ifname != null && bvs.device == ifname)
  601. uci.remove('network', bvs['.name']);
  602. });
  603. return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
  604. };
  605. s.handleAdd = function(ev) {
  606. var m2 = new form.Map('network'),
  607. s2 = m2.section(form.NamedSection, '_new_'),
  608. protocols = network.getProtocols(),
  609. proto, name, bridge, ifname_single, ifname_multi;
  610. protocols.sort(function(a, b) {
  611. return a.getProtocol() > b.getProtocol();
  612. });
  613. s2.render = function() {
  614. return Promise.all([
  615. {},
  616. this.renderUCISection('_new_')
  617. ]).then(this.renderContents.bind(this));
  618. };
  619. name = s2.option(form.Value, 'name', _('Name'));
  620. name.rmempty = false;
  621. name.datatype = 'uciname';
  622. name.placeholder = _('New interface name…');
  623. name.validate = function(section_id, value) {
  624. if (uci.get('network', value) != null)
  625. return _('The interface name is already used');
  626. var pr = network.getProtocol(proto.formvalue(section_id), value),
  627. ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value);
  628. if (value.length > 15)
  629. return _('The interface name is too long');
  630. return true;
  631. };
  632. proto = s2.option(form.ListValue, 'proto', _('Protocol'));
  633. proto.validate = name.validate;
  634. bridge = s2.option(form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
  635. bridge.modalonly = true;
  636. bridge.disabled = '';
  637. bridge.enabled = 'bridge';
  638. ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface'));
  639. ifname_single.noaliases = false;
  640. ifname_single.optional = false;
  641. ifname_multi = s2.option(widgets.DeviceSelect, 'ifname_multi', _('Interface'));
  642. ifname_multi.nobridges = true;
  643. ifname_multi.noaliases = true;
  644. ifname_multi.multiple = true;
  645. ifname_multi.optional = true;
  646. ifname_multi.display_size = 6;
  647. for (var i = 0; i < protocols.length; i++) {
  648. proto.value(protocols[i].getProtocol(), protocols[i].getI18n());
  649. if (!protocols[i].isVirtual()) {
  650. bridge.depends({ proto: protocols[i].getProtocol() });
  651. ifname_single.depends({ type: '', proto: protocols[i].getProtocol() });
  652. ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
  653. }
  654. }
  655. m2.render().then(L.bind(function(nodes) {
  656. ui.showModal(_('Add new interface...'), [
  657. nodes,
  658. E('div', { 'class': 'right' }, [
  659. E('button', {
  660. 'class': 'btn',
  661. 'click': ui.hideModal
  662. }, _('Cancel')), ' ',
  663. E('button', {
  664. 'class': 'cbi-button cbi-button-positive important',
  665. 'click': ui.createHandlerFn(this, function(ev) {
  666. var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
  667. protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null,
  668. protoclass = protoval ? network.getProtocol(protoval, nameval) : null;
  669. if (nameval == null || protoval == null || nameval == '' || protoval == '')
  670. return;
  671. return protoclass.isCreateable(nameval).then(function(checkval) {
  672. if (checkval != null) {
  673. ui.addNotification(null,
  674. E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval)));
  675. ui.hideModal();
  676. return;
  677. }
  678. return m.save(function() {
  679. uci.add('network', 'interface', nameval);
  680. protoclass.set('proto', protoval);
  681. if (ifname_single.isActive('_new_')) {
  682. protoclass.addDevice(ifname_single.formvalue('_new_'));
  683. }
  684. else if (ifname_multi.isActive('_new_')) {
  685. protoclass.set('type', 'bridge');
  686. L.toArray(ifname_multi.formvalue('_new_')).map(function(dev) {
  687. protoclass.addDevice(dev);
  688. });
  689. }
  690. m.children[0].addedSection = section_id;
  691. }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
  692. });
  693. })
  694. }, _('Create interface'))
  695. ])
  696. ], 'cbi-modal');
  697. nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
  698. }, this));
  699. };
  700. s.handleRemove = function(section_id, ev) {
  701. return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) {
  702. return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]);
  703. }, this, section_id, ev));
  704. };
  705. o = s.option(form.DummyValue, '_ifacebox');
  706. o.modalonly = false;
  707. o.textvalue = function(section_id) {
  708. var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0],
  709. zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null;
  710. if (!net)
  711. return;
  712. var node = E('div', { 'class': 'ifacebox' }, [
  713. E('div', {
  714. 'class': 'ifacebox-head',
  715. 'style': 'background-color:%s'.format(zone ? zone.getColor() : '#EEEEEE'),
  716. 'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
  717. }, E('strong', net.getName().toUpperCase())),
  718. E('div', {
  719. 'class': 'ifacebox-body',
  720. 'id': '%s-ifc-devices'.format(section_id),
  721. 'data-network': section_id
  722. }, [
  723. E('img', {
  724. 'src': L.resource('icons/ethernet_disabled.png'),
  725. 'style': 'width:16px; height:16px'
  726. }),
  727. E('br'), E('small', '?')
  728. ])
  729. ]);
  730. render_ifacebox_status(node.childNodes[1], net);
  731. return node;
  732. };
  733. o = s.option(form.DummyValue, '_ifacestat');
  734. o.modalonly = false;
  735. o.textvalue = function(section_id) {
  736. var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0];
  737. if (!net)
  738. return;
  739. var node = E('div', { 'id': '%s-ifc-description'.format(section_id) });
  740. render_status(node, net, false);
  741. return node;
  742. };
  743. o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management'));
  744. o.modalonly = true;
  745. o.default = o.enabled;
  746. o = s.taboption('advanced', form.Flag, 'force_link', _('Force link'), _('Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers).'));
  747. o.modalonly = true;
  748. o.defaults = {
  749. '1': [{ proto: 'static' }],
  750. '0': []
  751. };
  752. // Device configuration
  753. s = m.section(form.GridSection, 'device', _('Devices'));
  754. s.addremove = true;
  755. s.anonymous = true;
  756. s.addbtntitle = _('Add device configuration…');
  757. s.cfgsections = function() {
  758. var sections = uci.sections('network', 'device'),
  759. section_ids = sections.sort(function(a, b) { return a.name > b.name }).map(function(s) { return s['.name'] });
  760. for (var i = 0; i < netDevs.length; i++) {
  761. if (sections.filter(function(s) { return s.name == netDevs[i].getName() }).length)
  762. continue;
  763. if (netDevs[i].getType() == 'wifi' && !netDevs[i].isUp())
  764. continue;
  765. /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
  766. we cannot properly redefine bridges as devices, so filter them away for now... */
  767. var m = netDevs[i].isBridge() ? netDevs[i].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
  768. s = m ? uci.get('network', m[1]) : null;
  769. if (s && s['.type'] == 'interface' && s.type == 'bridge')
  770. continue;
  771. section_ids.push('dev:%s'.format(netDevs[i].getName()));
  772. }
  773. return section_ids;
  774. };
  775. s.renderMoreOptionsModal = function(section_id, ev) {
  776. var m = section_id.match(/^dev:(.+)$/);
  777. if (m) {
  778. var devtype = getDevType(section_id);
  779. section_id = uci.add('network', 'device');
  780. uci.set('network', section_id, 'name', m[1]);
  781. uci.set('network', section_id, 'type', (devtype != 'ethernet') ? devtype : null);
  782. this.addedSection = section_id;
  783. }
  784. return this.super('renderMoreOptionsModal', [section_id, ev]);
  785. };
  786. s.renderRowActions = function(section_id) {
  787. var trEl = this.super('renderRowActions', [ section_id, _('Configure…') ]),
  788. deleteBtn = trEl.querySelector('button:last-child');
  789. deleteBtn.firstChild.data = _('Reset');
  790. deleteBtn.disabled = section_id.match(/^dev:/) ? true : null;
  791. return trEl;
  792. };
  793. s.modaltitle = function(section_id) {
  794. var m = section_id.match(/^dev:(.+)$/),
  795. name = m ? m[1] : uci.get('network', section_id, 'name');
  796. return name ? '%s: %q'.format(getDevTypeDesc(section_id), name) : _('Add device configuration');
  797. };
  798. s.addModalOptions = function(s) {
  799. var isNew = (uci.get('network', s.section, 'name') == null),
  800. dev = getDevice(s.section);
  801. nettools.addDeviceOptions(s, dev, isNew);
  802. };
  803. s.handleModalCancel = function(/* ... */) {
  804. var name = uci.get('network', this.addedSection, 'name')
  805. uci.sections('network', 'bridge-vlan', function(bvs) {
  806. if (name != null && bvs.device == name)
  807. uci.remove('network', bvs['.name']);
  808. });
  809. return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
  810. };
  811. function getDevice(section_id) {
  812. var m = section_id.match(/^dev:(.+)$/),
  813. name = m ? m[1] : uci.get('network', section_id, 'name');
  814. return netDevs.filter(function(d) { return d.getName() == name })[0];
  815. }
  816. function getDevType(section_id) {
  817. var cfgtype = uci.get('network', section_id, 'type'),
  818. dev = getDevice(section_id);
  819. switch (cfgtype || (dev ? dev.getType() : '')) {
  820. case '':
  821. return null;
  822. case 'vlan':
  823. case '8021q':
  824. return '8021q';
  825. case '8021ad':
  826. return '8021ad';
  827. case 'bridge':
  828. return 'bridge';
  829. case 'tunnel':
  830. return 'tunnel';
  831. case 'macvlan':
  832. return 'macvlan';
  833. case 'veth':
  834. return 'veth';
  835. case 'wifi':
  836. case 'alias':
  837. case 'switch':
  838. case 'ethernet':
  839. default:
  840. return 'ethernet';
  841. }
  842. }
  843. function getDevTypeDesc(section_id) {
  844. switch (getDevType(section_id) || '') {
  845. case '':
  846. return E('em', [ _('Device not present') ]);
  847. case '8021q':
  848. return _('VLAN (802.1q)');
  849. case '8021ad':
  850. return _('VLAN (802.1ad)');
  851. case 'bridge':
  852. return _('Bridge device');
  853. case 'tunnel':
  854. return _('Tunnel device');
  855. case 'macvlan':
  856. return _('MAC VLAN');
  857. case 'veth':
  858. return _('Virtual Ethernet');
  859. default:
  860. return _('Network device');
  861. }
  862. }
  863. o = s.option(form.DummyValue, 'name', _('Device'));
  864. o.modalonly = false;
  865. o.textvalue = function(section_id) {
  866. var dev = getDevice(section_id),
  867. ext = section_id.match(/^dev:/);
  868. return E('span', {
  869. 'class': 'ifacebadge',
  870. 'style': ext ? 'opacity:.5' : null
  871. }, [
  872. render_iface(dev), ' ', dev ? dev.getName() : (uci.get('network', section_id, 'name') || '?')
  873. ]);
  874. };
  875. o = s.option(form.DummyValue, 'type', _('Type'));
  876. o.textvalue = getDevTypeDesc;
  877. o.modalonly = false;
  878. o = s.option(form.DummyValue, 'macaddr', _('MAC Address'));
  879. o.modalonly = false;
  880. o.textvalue = function(section_id) {
  881. var dev = getDevice(section_id),
  882. val = uci.get('network', section_id, 'macaddr'),
  883. mac = dev ? dev.getMAC() : null;
  884. return val ? E('strong', {
  885. 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mac || _('unknown'))
  886. }, [ val.toUpperCase() ]) : (mac || '-');
  887. };
  888. o = s.option(form.DummyValue, 'mtu', _('MTU'));
  889. o.modalonly = false;
  890. o.textvalue = function(section_id) {
  891. var dev = getDevice(section_id),
  892. val = uci.get('network', section_id, 'mtu'),
  893. mtu = dev ? dev.getMTU() : null;
  894. return val ? E('strong', {
  895. 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mtu || _('unknown'))
  896. }, [ val ]) : (mtu || '-').toString();
  897. };
  898. s = m.section(form.TypedSection, 'globals', _('Global network options'));
  899. s.addremove = false;
  900. s.anonymous = true;
  901. o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'));
  902. o.datatype = 'cidr6';
  903. o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
  904. o.optional = true;
  905. if (dslModemType != null) {
  906. s = m.section(form.TypedSection, 'dsl', _('DSL'));
  907. s.anonymous = true;
  908. o = s.option(form.ListValue, 'annex', _('Annex'));
  909. o.value('a', _('Annex A + L + M (all)'));
  910. o.value('b', _('Annex B (all)'));
  911. o.value('j', _('Annex J (all)'));
  912. o.value('m', _('Annex M (all)'));
  913. o.value('bdmt', _('Annex B G.992.1'));
  914. o.value('b2', _('Annex B G.992.3'));
  915. o.value('b2p', _('Annex B G.992.5'));
  916. o.value('at1', _('ANSI T1.413'));
  917. o.value('admt', _('Annex A G.992.1'));
  918. o.value('alite', _('Annex A G.992.2'));
  919. o.value('a2', _('Annex A G.992.3'));
  920. o.value('a2p', _('Annex A G.992.5'));
  921. o.value('l', _('Annex L G.992.3 POTS 1'));
  922. o.value('m2', _('Annex M G.992.3'));
  923. o.value('m2p', _('Annex M G.992.5'));
  924. o = s.option(form.ListValue, 'tone', _('Tone'));
  925. o.value('', _('auto'));
  926. o.value('a', _('A43C + J43 + A43'));
  927. o.value('av', _('A43C + J43 + A43 + V43'));
  928. o.value('b', _('B43 + B43C'));
  929. o.value('bv', _('B43 + B43C + V43'));
  930. if (dslModemType == 'vdsl') {
  931. o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode'));
  932. o.value('', _('auto'));
  933. o.value('atm', _('ATM (Asynchronous Transfer Mode)'));
  934. o.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
  935. o = s.option(form.ListValue, 'line_mode', _('DSL line mode'));
  936. o.value('', _('auto'));
  937. o.value('adsl', _('ADSL'));
  938. o.value('vdsl', _('VDSL'));
  939. o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset'));
  940. o.default = '0';
  941. for (var i = -100; i <= 100; i += 5)
  942. o.value(i, _('%.1f dB').format(i / 10));
  943. }
  944. s.option(form.Value, 'firmware', _('Firmware File'));
  945. }
  946. // Show ATM bridge section if we have the capabilities
  947. if (L.hasSystemFeature('br2684ctl')) {
  948. s = m.section(form.TypedSection, 'atm-bridge', _('ATM Bridges'), _('ATM bridges expose encapsulated ethernet in AAL5 connections as virtual Linux network interfaces which can be used in conjunction with DHCP or PPP to dial into the provider network.'));
  949. s.addremove = true;
  950. s.anonymous = true;
  951. s.addbtntitle = _('Add ATM Bridge');
  952. s.handleAdd = function(ev) {
  953. var sections = uci.sections('network', 'atm-bridge'),
  954. max_unit = -1;
  955. for (var i = 0; i < sections.length; i++) {
  956. var unit = +sections[i].unit;
  957. if (!isNaN(unit) && unit > max_unit)
  958. max_unit = unit;
  959. }
  960. return this.map.save(function() {
  961. var sid = uci.add('network', 'atm-bridge');
  962. uci.set('network', sid, 'unit', max_unit + 1);
  963. uci.set('network', sid, 'atmdev', 0);
  964. uci.set('network', sid, 'encaps', 'llc');
  965. uci.set('network', sid, 'payload', 'bridged');
  966. uci.set('network', sid, 'vci', 35);
  967. uci.set('network', sid, 'vpi', 8);
  968. });
  969. };
  970. s.tab('general', _('General Setup'));
  971. s.tab('advanced', _('Advanced Settings'));
  972. o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
  973. s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
  974. o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode'));
  975. o.value('llc', _('LLC'));
  976. o.value('vc', _('VC-Mux'));
  977. s.taboption('advanced', form.Value, 'atmdev', _('ATM device number'));
  978. s.taboption('advanced', form.Value, 'unit', _('Bridge unit number'));
  979. o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode'));
  980. o.value('bridged', _('bridged'));
  981. o.value('routed', _('routed'));
  982. }
  983. return m.render().then(L.bind(function(m, nodes) {
  984. poll.add(L.bind(function() {
  985. var section_ids = m.children[0].cfgsections(),
  986. tasks = [];
  987. for (var i = 0; i < section_ids.length; i++) {
  988. var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
  989. dsc = row.querySelector('[data-name="_ifacestat"] > div'),
  990. btn1 = row.querySelector('.cbi-section-actions .reconnect'),
  991. btn2 = row.querySelector('.cbi-section-actions .down');
  992. if (dsc.getAttribute('reconnect') == '') {
  993. dsc.setAttribute('reconnect', '1');
  994. tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) {
  995. ui.addNotification(null, E('p', e.message));
  996. }));
  997. }
  998. else if (dsc.getAttribute('disconnect') == '') {
  999. dsc.setAttribute('disconnect', '1');
  1000. tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) {
  1001. ui.addNotification(null, E('p', e.message));
  1002. }));
  1003. }
  1004. else if (dsc.getAttribute('reconnect') == '1') {
  1005. dsc.removeAttribute('reconnect');
  1006. btn1.classList.remove('spinning');
  1007. btn1.disabled = false;
  1008. }
  1009. else if (dsc.getAttribute('disconnect') == '1') {
  1010. dsc.removeAttribute('disconnect');
  1011. btn2.classList.remove('spinning');
  1012. btn2.disabled = false;
  1013. }
  1014. }
  1015. return Promise.all(tasks)
  1016. .then(L.bind(network.getNetworks, network))
  1017. .then(L.bind(this.poll_status, this, nodes));
  1018. }, this), 5);
  1019. return nodes;
  1020. }, this, m));
  1021. }
  1022. });