11 'require tools.widgets as widgets';
13 var isReadonlyView
= !L
.hasViewPermission() || null;
15 function count_changes(section_id
) {
16 var changes
= ui
.changes
.changes
, n
= 0;
18 if (!L
.isObject(changes
))
21 if (Array
.isArray(changes
.network
))
22 for (var i
= 0; i
< changes
.network
.length
; i
++)
23 n
+= (changes
.network
[i
][1] == section_id
);
25 if (Array
.isArray(changes
.dhcp
))
26 for (var i
= 0; i
< changes
.dhcp
.length
; i
++)
27 n
+= (changes
.dhcp
[i
][1] == section_id
);
32 function render_iface(dev
, alias
) {
33 var type
= dev
? dev
.getType() : 'ethernet',
34 up
= dev
? dev
.isUp() : false;
36 return E('span', { class: 'cbi-tooltip-container' }, [
37 E('img', { 'class' : 'middle', 'src': L
.resource('icons/%s%s.png').format(
38 alias
? 'alias' : type
,
39 up
? '' : '_disabled') }),
40 E('span', { 'class': 'cbi-tooltip ifacebadge large' }, [
41 E('img', { 'src': L
.resource('icons/%s%s.png').format(
42 type
, up
? '' : '_disabled') }),
43 L
.itemlist(E('span', { 'class': 'left' }), [
44 _('Type'), dev
? dev
.getTypeI18n() : null,
45 _('Device'), dev
? dev
.getName() : _('Not present'),
46 _('Connected'), up
? _('yes') : _('no'),
47 _('MAC'), dev
? dev
.getMAC() : null,
48 _('RX'), dev
? '%.2mB (%d %s)'.format(dev
.getRXBytes(), dev
.getRXPackets(), _('Pkts.')) : null,
49 _('TX'), dev
? '%.2mB (%d %s)'.format(dev
.getTXBytes(), dev
.getTXPackets(), _('Pkts.')) : null
55 function render_status(node
, ifc
, with_device
) {
56 var desc
= null, c
= [];
59 desc
= _('Virtual dynamic interface');
60 else if (ifc
.isAlias())
61 desc
= _('Alias Interface');
62 else if (!uci
.get('network', ifc
.getName()))
63 return L
.itemlist(node
, [
64 null, E('em', _('Interface is marked for deletion'))
67 var i18n
= ifc
.getI18n();
69 desc
= desc
? '%s (%s)'.format(desc
, i18n
) : i18n
;
71 var changecount
= with_device
? 0 : count_changes(ifc
.getName()),
72 ipaddrs
= changecount
? [] : ifc
.getIPAddrs(),
73 ip6addrs
= changecount
? [] : ifc
.getIP6Addrs(),
74 errors
= ifc
.getErrors(),
75 maindev
= ifc
.getL3Device() || ifc
.getDevice(),
76 macaddr
= maindev
? maindev
.getMAC() : null;
78 return L
.itemlist(node
, [
79 _('Protocol'), with_device
? null : (desc
|| '?'),
80 _('Device'), with_device
? (maindev
? maindev
.getShortName() : E('em', _('Not present'))) : null,
81 _('Uptime'), (!changecount
&& ifc
.isUp()) ? '%t'.format(ifc
.getUptime()) : null,
82 _('MAC'), (!changecount
&& !ifc
.isDynamic() && !ifc
.isAlias() && macaddr
) ? macaddr
: null,
83 _('RX'), (!changecount
&& !ifc
.isDynamic() && !ifc
.isAlias() && maindev
) ? '%.2mB (%d %s)'.format(maindev
.getRXBytes(), maindev
.getRXPackets(), _('Pkts.')) : null,
84 _('TX'), (!changecount
&& !ifc
.isDynamic() && !ifc
.isAlias() && maindev
) ? '%.2mB (%d %s)'.format(maindev
.getTXBytes(), maindev
.getTXPackets(), _('Pkts.')) : null,
85 _('IPv4'), ipaddrs
[0],
86 _('IPv4'), ipaddrs
[1],
87 _('IPv4'), ipaddrs
[2],
88 _('IPv4'), ipaddrs
[3],
89 _('IPv4'), ipaddrs
[4],
90 _('IPv6'), ip6addrs
[0],
91 _('IPv6'), ip6addrs
[1],
92 _('IPv6'), ip6addrs
[2],
93 _('IPv6'), ip6addrs
[3],
94 _('IPv6'), ip6addrs
[4],
95 _('IPv6'), ip6addrs
[5],
96 _('IPv6'), ip6addrs
[6],
97 _('IPv6'), ip6addrs
[7],
98 _('IPv6'), ip6addrs
[8],
99 _('IPv6'), ip6addrs
[9],
100 _('IPv6-PD'), changecount
? null : ifc
.getIP6Prefix(),
101 _('Information'), with_device
? null : (ifc
.get('auto') != '0' ? null : _('Not started on boot')),
102 _('Error'), errors
? errors
[0] : null,
103 _('Error'), errors
? errors
[1] : null,
104 _('Error'), errors
? errors
[2] : null,
105 _('Error'), errors
? errors
[3] : null,
106 _('Error'), errors
? errors
[4] : null,
107 null, changecount
? E('a', {
109 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
110 }, _('Interface has %d pending changes').format(changecount
)) : null
114 function render_modal_status(node
, ifc
) {
115 var dev
= ifc
? (ifc
.getDevice() || ifc
.getL3Device() || ifc
.getL3Device()) : null;
119 'src': L
.resource('icons/%s%s.png').format(dev
? dev
.getType() : 'ethernet', (dev
&& dev
.isUp()) ? '' : '_disabled'),
120 'title': dev
? dev
.getTypeI18n() : _('Not present')
122 ifc
? render_status(E('span'), ifc
, true) : E('em', _('Interface not present or not connected yet.'))
128 function render_ifacebox_status(node
, ifc
) {
129 var dev
= ifc
.getL3Device() || ifc
.getDevice(),
130 subdevs
= ifc
.getDevices(),
131 c
= [ render_iface(dev
, ifc
.isAlias()) ];
133 if (subdevs
&& subdevs
.length
) {
136 for (var j
= 0; j
< subdevs
.length
; j
++)
137 sifs
.push(render_iface(subdevs
[j
]));
141 c
.push(E('span', {}, sifs
));
145 c
.push(E('small', {}, ifc
.isAlias() ? _('Alias of "%s"').format(ifc
.isAlias())
146 : (dev
? dev
.getName() : E('em', _('Not present')))));
148 dom
.content(node
, c
);
150 return firewall
.getZoneByNetwork(ifc
.getName()).then(L
.bind(function(zone
) {
151 this.style
.backgroundColor
= zone
? zone
.getColor() : '#EEEEEE';
152 this.title
= zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned');
153 }, node
.previousElementSibling
));
156 function iface_updown(up
, id
, ev
, force
) {
157 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
158 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
159 btns
= row
.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down');
162 btns
[+!up
].classList
.add('spinning');
164 btns
[0].disabled
= true;
165 btns
[1].disabled
= true;
168 L
.resolveDefault(fs
.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res
) {
169 var info
= null; try { info
= JSON
.parse(res
); } catch(e
) {}
171 if (L
.isObject(info
) &&
172 Array
.isArray(info
.inbound_interfaces
) &&
173 info
.inbound_interfaces
.filter(function(i
) { return i
== id
})[0]) {
175 ui
.showModal(_('Confirm disconnect'), [
176 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
)),
177 E('div', { 'class': 'right' }, [
179 'class': 'cbi-button cbi-button-neutral',
180 'click': function(ev
) {
181 btns
[1].classList
.remove('spinning');
182 btns
[1].disabled
= false;
183 btns
[0].disabled
= false;
190 'class': 'cbi-button cbi-button-negative important',
191 'click': function(ev
) {
192 dsc
.setAttribute('disconnect', '');
193 dom
.content(dsc
, E('em', _('Interface is shutting down...')));
202 dsc
.setAttribute('disconnect', '');
203 dom
.content(dsc
, E('em', _('Interface is shutting down...')));
208 dsc
.setAttribute(up
? 'reconnect' : 'disconnect', force
? 'force' : '');
209 dom
.content(dsc
, E('em', up
? _('Interface is reconnecting...') : _('Interface is shutting down...')));
213 function get_netmask(s
, use_cfgvalue
) {
214 var readfn
= use_cfgvalue
? 'cfgvalue' : 'formvalue',
215 addropt
= s
.children
.filter(function(o
) { return o
.option
== 'ipaddr'})[0],
216 addrvals
= addropt
? L
.toArray(addropt
[readfn
](s
.section
)) : [],
217 maskopt
= s
.children
.filter(function(o
) { return o
.option
== 'netmask'})[0],
218 maskval
= maskopt
? maskopt
[readfn
](s
.section
) : null,
219 firstsubnet
= maskval
? addrvals
[0] + '/' + maskval
: addrvals
.filter(function(a
) { return a
.indexOf('/') > 0 })[0];
221 if (firstsubnet
== null)
224 var mask
= firstsubnet
.split('/')[1];
227 mask
= network
.prefixToMask(+mask
);
233 poll_status: function(map
, networks
) {
234 var resolveZone
= null;
236 for (var i
= 0; i
< networks
.length
; i
++) {
237 var ifc
= networks
[i
],
238 row
= map
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc
.getName()));
243 var dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
244 box
= row
.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
245 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
246 btn2
= row
.querySelector('.cbi-section-actions .down'),
247 stat
= document
.querySelector('[id="%s-ifc-status"]'.format(ifc
.getName())),
248 resolveZone
= render_ifacebox_status(box
, ifc
),
249 disabled
= ifc
? !ifc
.isUp() : true,
250 dynamic
= ifc
? ifc
.isDynamic() : false;
252 if (dsc
.hasAttribute('reconnect')) {
253 dom
.content(dsc
, E('em', _('Interface is starting...')));
255 else if (dsc
.hasAttribute('disconnect')) {
256 dom
.content(dsc
, E('em', _('Interface is stopping...')));
258 else if (ifc
.getProtocol() || uci
.get('network', ifc
.getName()) == null) {
259 render_status(dsc
, ifc
, false);
261 else if (!ifc
.getProtocol()) {
262 var e
= map
.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc
.getName()));
263 if (e
) e
.disabled
= true;
265 var link
= L
.url('admin/system/opkg') + '?query=luci-proto';
267 E('em', _('Unsupported protocol type.')), E('br'),
268 E('a', { href
: link
}, _('Install protocol extensions...'))
272 dom
.content(dsc
, E('em', _('Interface not present or not connected yet.')));
276 var dev
= ifc
.getDevice();
279 'src': L
.resource('icons/%s%s.png').format(dev
? dev
.getType() : 'ethernet', (dev
&& dev
.isUp()) ? '' : '_disabled'),
280 'title': dev
? dev
.getTypeI18n() : _('Not present')
282 render_status(E('span'), ifc
, true)
286 btn1
.disabled
= isReadonlyView
|| btn1
.classList
.contains('spinning') || btn2
.classList
.contains('spinning') || dynamic
;
287 btn2
.disabled
= isReadonlyView
|| btn1
.classList
.contains('spinning') || btn2
.classList
.contains('spinning') || dynamic
|| disabled
;
290 return Promise
.all([ resolveZone
, network
.flushCache() ]);
295 network
.getDSLModemType(),
300 render: function(data
) {
301 var dslModemType
= data
[0],
304 m
= new form
.Map('network');
308 s
= m
.section(form
.GridSection
, 'interface', _('Interfaces'));
311 s
.addbtntitle
= _('Add new interface...');
313 s
.load = function() {
315 network
.getNetworks(),
317 ]).then(L
.bind(function(data
) {
318 this.networks
= data
[0];
319 this.zones
= data
[1];
323 s
.tab('general', _('General Settings'));
324 s
.tab('advanced', _('Advanced Settings'));
325 s
.tab('physical', _('Physical Settings'));
326 s
.tab('firewall', _('Firewall Settings'));
327 s
.tab('dhcp', _('DHCP Server'));
329 s
.cfgsections = function() {
330 return this.networks
.map(function(n
) { return n
.getName() })
331 .filter(function(n
) { return n
!= 'loopback' });
334 s
.modaltitle = function(section_id
) {
335 return _('Interfaces') + ' » ' + section_id
.toUpperCase();
338 s
.renderRowActions = function(section_id
) {
339 var tdEl
= this.super('renderRowActions', [ section_id
, _('Edit') ]),
340 net
= this.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
341 disabled
= net
? !net
.isUp() : true,
342 dynamic
= net
? net
.isDynamic() : false;
344 dom
.content(tdEl
.lastChild
, [
346 'class': 'cbi-button cbi-button-neutral reconnect',
347 'click': iface_updown
.bind(this, true, section_id
),
348 'title': _('Reconnect this interface'),
349 'disabled': dynamic
? 'disabled' : null
352 'class': 'cbi-button cbi-button-neutral down',
353 'click': iface_updown
.bind(this, false, section_id
),
354 'title': _('Shutdown this interface'),
355 'disabled': (dynamic
|| disabled
) ? 'disabled' : null
357 tdEl
.lastChild
.firstChild
,
358 tdEl
.lastChild
.lastChild
361 if (!dynamic
&& net
&& !uci
.get('network', net
.getName())) {
362 tdEl
.lastChild
.childNodes
[0].disabled
= true;
363 tdEl
.lastChild
.childNodes
[2].disabled
= true;
364 tdEl
.lastChild
.childNodes
[3].disabled
= true;
370 s
.addModalOptions = function(s
) {
371 var protoval
= uci
.get('network', s
.section
, 'proto'),
372 protoclass
= protoval
? network
.getProtocol(protoval
) : null,
373 o
, ifname_single
, ifname_multi
, proto_select
, proto_switch
, type
, stp
, igmp
, ss
, so
;
378 return network
.getNetwork(s
.section
).then(L
.bind(function(ifc
) {
379 var protocols
= network
.getProtocols();
381 protocols
.sort(function(a
, b
) {
382 return a
.getProtocol() > b
.getProtocol();
385 o
= s
.taboption('general', form
.DummyValue
, '_ifacestat_modal', _('Status'));
387 o
.cfgvalue
= L
.bind(function(section_id
) {
388 var net
= this.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
390 return render_modal_status(E('div', {
391 'id': '%s-ifc-status'.format(section_id
),
392 'class': 'ifacebadge large'
395 o
.write = function() {};
397 proto_select
= s
.taboption('general', form
.ListValue
, 'proto', _('Protocol'));
398 proto_select
.modalonly
= true;
400 proto_switch
= s
.taboption('general', form
.Button
, '_switch_proto');
401 proto_switch
.modalonly
= true;
402 proto_switch
.title
= _('Really switch protocol?');
403 proto_switch
.inputtitle
= _('Switch protocol');
404 proto_switch
.inputstyle
= 'apply';
405 proto_switch
.onclick
= L
.bind(function(ev
) {
407 .then(L
.bind(m
.load
, m
))
408 .then(L
.bind(m
.render
, m
))
409 .then(L
.bind(this.renderMoreOptionsModal
, this, s
.section
));
412 o
= s
.taboption('general', form
.Flag
, 'auto', _('Bring up on boot'));
414 o
.default = o
.enabled
;
416 type
= s
.taboption('physical', form
.Flag
, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
417 type
.modalonly
= true;
419 type
.enabled
= 'bridge';
420 type
.write
= type
.remove = function(section_id
, value
) {
421 var protocol
= network
.getProtocol(proto_select
.formvalue(section_id
)),
422 ifnameopt
= this.section
.children
.filter(function(o
) { return o
.option
== (value
? 'ifname_multi' : 'ifname_single') })[0];
424 if (!protocol
.isVirtual() && !this.isActive(section_id
))
427 var old_ifnames
= [],
428 devs
= ifc
.getDevices() || L
.toArray(ifc
.getDevice());
430 for (var i
= 0; i
< devs
.length
; i
++)
431 old_ifnames
.push(devs
[i
].getName());
433 var new_ifnames
= L
.toArray(ifnameopt
.formvalue(section_id
));
436 new_ifnames
.length
= Math
.max(new_ifnames
.length
, 1);
441 for (var i
= 0; i
< Math
.max(old_ifnames
.length
, new_ifnames
.length
); i
++) {
442 if (old_ifnames
[i
] != new_ifnames
[i
]) {
444 for (var j
= 0; j
< old_ifnames
.length
; j
++)
445 ifc
.deleteDevice(old_ifnames
[j
]);
447 for (var j
= 0; j
< new_ifnames
.length
; j
++)
448 ifc
.addDevice(new_ifnames
[j
]);
455 uci
.set('network', section_id
, 'type', 'bridge');
457 uci
.unset('network', section_id
, 'type');
460 stp
= s
.taboption('physical', form
.Flag
, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
462 igmp
= s
.taboption('physical', form
.Flag
, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> snooping'), _('Enables IGMP snooping on this bridge'));
464 ifname_single
= s
.taboption('physical', widgets
.DeviceSelect
, 'ifname_single', _('Interface'));
465 ifname_single
.nobridges
= ifc
.isBridge();
466 ifname_single
.noaliases
= false;
467 ifname_single
.optional
= false;
468 ifname_single
.network
= ifc
.getName();
469 ifname_single
.write
= ifname_single
.remove = function() {};
471 ifname_multi
= s
.taboption('physical', widgets
.DeviceSelect
, 'ifname_multi', _('Interface'));
472 ifname_multi
.nobridges
= ifc
.isBridge();
473 ifname_multi
.noaliases
= true;
474 ifname_multi
.multiple
= true;
475 ifname_multi
.optional
= true;
476 ifname_multi
.network
= ifc
.getName();
477 ifname_multi
.display_size
= 6;
478 ifname_multi
.write
= ifname_multi
.remove = function() {};
480 ifname_single
.cfgvalue
= ifname_multi
.cfgvalue = function(section_id
) {
481 var devs
= ifc
.getDevices() || L
.toArray(ifc
.getDevice()),
484 for (var i
= 0; i
< devs
.length
; i
++)
485 ifnames
.push(devs
[i
].getName());
490 if (L
.hasSystemFeature('firewall')) {
491 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.'));
492 o
.network
= ifc
.getName();
495 o
.cfgvalue = function(section_id
) {
496 return firewall
.getZoneByNetwork(ifc
.getName()).then(function(zone
) {
497 return (zone
!= null ? zone
.getName() : null);
501 o
.write
= o
.remove = function(section_id
, value
) {
503 firewall
.getZoneByNetwork(ifc
.getName()),
504 (value
!= null) ? firewall
.getZone(value
) : null
505 ]).then(function(data
) {
506 var old_zone
= data
[0],
509 if (old_zone
== null && new_zone
== null && (value
== null || value
== ''))
512 if (old_zone
!= null && new_zone
!= null && old_zone
.getName() == new_zone
.getName())
515 if (old_zone
!= null)
516 old_zone
.deleteNetwork(ifc
.getName());
518 if (new_zone
!= null)
519 new_zone
.addNetwork(ifc
.getName());
520 else if (value
!= null)
521 return firewall
.addZone(value
).then(function(new_zone
) {
522 new_zone
.addNetwork(ifc
.getName());
528 for (var i
= 0; i
< protocols
.length
; i
++) {
529 proto_select
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
531 if (protocols
[i
].getProtocol() != uci
.get('network', s
.section
, 'proto'))
532 proto_switch
.depends('proto', protocols
[i
].getProtocol());
534 if (!protocols
[i
].isVirtual()) {
535 type
.depends('proto', protocols
[i
].getProtocol());
536 stp
.depends({ type
: 'bridge', proto
: protocols
[i
].getProtocol() });
537 igmp
.depends({ type
: 'bridge', proto
: protocols
[i
].getProtocol() });
538 ifname_single
.depends({ type
: '', proto
: protocols
[i
].getProtocol() });
539 ifname_multi
.depends({ type
: 'bridge', proto
: protocols
[i
].getProtocol() });
543 if (L
.hasSystemFeature('dnsmasq') || L
.hasSystemFeature('odhcpd')) {
544 o
= s
.taboption('dhcp', form
.SectionValue
, '_dhcp', form
.TypedSection
, 'dhcp');
545 o
.depends('proto', 'static');
548 ss
.uciconfig
= 'dhcp';
549 ss
.addremove
= false;
552 ss
.tab('general', _('General Setup'));
553 ss
.tab('advanced', _('Advanced Settings'));
554 ss
.tab('ipv6', _('IPv6 Settings'));
556 ss
.filter = function(section_id
) {
557 return (uci
.get('dhcp', section_id
, 'interface') == ifc
.getName());
560 ss
.renderSectionPlaceholder = function() {
561 return E('div', { 'class': 'cbi-section-create' }, [
562 E('p', _('No DHCP Server configured for this interface') + '   '),
564 'class': 'cbi-button cbi-button-add',
565 'title': _('Setup DHCP Server'),
566 'click': ui
.createHandlerFn(this, function(section_id
, ev
) {
567 this.map
.save(function() {
568 uci
.add('dhcp', 'dhcp', section_id
);
569 uci
.set('dhcp', section_id
, 'interface', section_id
);
570 uci
.set('dhcp', section_id
, 'start', 100);
571 uci
.set('dhcp', section_id
, 'limit', 150);
572 uci
.set('dhcp', section_id
, 'leasetime', '12h');
575 }, _('Setup DHCP Server'))
579 ss
.taboption('general', form
.Flag
, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
581 so
= ss
.taboption('general', form
.Value
, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
583 so
.datatype
= 'or(uinteger,ip4addr("nomask"))';
586 so
= ss
.taboption('general', form
.Value
, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
588 so
.datatype
= 'uinteger';
591 so
= ss
.taboption('general', form
.Value
, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
595 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.'));
596 so
.default = so
.enabled
;
598 ss
.taboption('advanced', form
.Flag
, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
600 // XXX: is this actually useful?
601 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
603 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.'));
605 so
.datatype
= 'ip4addr';
607 so
.render = function(option_index
, section_id
, in_table
) {
608 this.placeholder
= get_netmask(s
, true);
609 return form
.Value
.prototype.render
.apply(this, [ option_index
, section_id
, in_table
]);
612 so
.validate = function(section_id
, value
) {
613 var node
= this.map
.findElement('id', this.cbid(section_id
));
615 node
.querySelector('input').setAttribute('placeholder', get_netmask(s
, false));
616 return form
.Value
.prototype.validate
.apply(this, [ section_id
, value
]);
619 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.'));
621 for (var i
= 0; i
< ss
.children
.length
; i
++)
622 if (ss
.children
[i
].option
!= 'ignore')
623 ss
.children
[i
].depends('ignore', '0');
625 so
= ss
.taboption('ipv6', form
.ListValue
, 'ra', _('Router Advertisement-Service'));
626 so
.value('', _('disabled'));
627 so
.value('server', _('server mode'));
628 so
.value('relay', _('relay mode'));
629 so
.value('hybrid', _('hybrid mode'));
631 so
= ss
.taboption('ipv6', form
.ListValue
, 'dhcpv6', _('DHCPv6-Service'));
632 so
.value('', _('disabled'));
633 so
.value('server', _('server mode'));
634 so
.value('relay', _('relay mode'));
635 so
.value('hybrid', _('hybrid mode'));
637 so
= ss
.taboption('ipv6', form
.ListValue
, 'ndp', _('NDP-Proxy'));
638 so
.value('', _('disabled'));
639 so
.value('relay', _('relay mode'));
640 so
.value('hybrid', _('hybrid mode'));
642 so
= ss
.taboption('ipv6', form
.Flag
, 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.'));
643 so
.depends('dhcpv6', 'relay');
644 so
.depends('dhcpv6', 'hybrid');
646 so
= ss
.taboption('ipv6', form
.ListValue
, 'ra_management', _('DHCPv6-Mode'), _('Default is stateless + stateful'));
647 so
.value('0', _('stateless'));
648 so
.value('1', _('stateless + stateful'));
649 so
.value('2', _('stateful-only'));
650 so
.depends('dhcpv6', 'server');
651 so
.depends('dhcpv6', 'hybrid');
654 so
= ss
.taboption('ipv6', form
.Flag
, 'ra_default', _('Always announce default router'), _('Announce as default router even if no public prefix is available.'));
655 so
.depends('ra', 'server');
656 so
.depends('ra', 'hybrid');
658 ss
.taboption('ipv6', form
.DynamicList
, 'dns', _('Announced DNS servers'));
659 ss
.taboption('ipv6', form
.DynamicList
, 'domain', _('Announced DNS domains'));
662 ifc
.renderFormOptions(s
);
664 for (var i
= 0; i
< s
.children
.length
; i
++) {
673 case 'igmp_snooping':
674 case 'ifname_single':
678 case '_switch_proto':
679 case '_ifacestat_modal':
684 for (var j
= 0; j
< o
.deps
.length
; j
++)
685 o
.deps
[j
].proto
= protoval
;
687 o
.depends('proto', protoval
);
693 s
.handleAdd = function(ev
) {
694 var m2
= new form
.Map('network'),
695 s2
= m2
.section(form
.NamedSection
, '_new_'),
696 protocols
= network
.getProtocols(),
697 proto
, name
, bridge
, ifname_single
, ifname_multi
;
699 protocols
.sort(function(a
, b
) {
700 return a
.getProtocol() > b
.getProtocol();
703 s2
.render = function() {
706 this.renderUCISection('_new_')
707 ]).then(this.renderContents
.bind(this));
710 name
= s2
.option(form
.Value
, 'name', _('Name'));
711 name
.rmempty
= false;
712 name
.datatype
= 'uciname';
713 name
.placeholder
= _('New interface name…');
714 name
.validate = function(section_id
, value
) {
715 if (uci
.get('network', value
) != null)
716 return _('The interface name is already used');
718 var pr
= network
.getProtocol(proto
.formvalue(section_id
), value
),
719 ifname
= pr
.isVirtual() ? '%s-%s'.format(pr
.getProtocol(), value
) : 'br-%s'.format(value
);
721 if (value
.length
> 15)
722 return _('The interface name is too long');
727 proto
= s2
.option(form
.ListValue
, 'proto', _('Protocol'));
728 proto
.validate
= name
.validate
;
730 bridge
= s2
.option(form
.Flag
, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
731 bridge
.modalonly
= true;
732 bridge
.disabled
= '';
733 bridge
.enabled
= 'bridge';
735 ifname_single
= s2
.option(widgets
.DeviceSelect
, 'ifname_single', _('Interface'));
736 ifname_single
.noaliases
= false;
737 ifname_single
.optional
= false;
739 ifname_multi
= s2
.option(widgets
.DeviceSelect
, 'ifname_multi', _('Interface'));
740 ifname_multi
.nobridges
= true;
741 ifname_multi
.noaliases
= true;
742 ifname_multi
.multiple
= true;
743 ifname_multi
.optional
= true;
744 ifname_multi
.display_size
= 6;
746 for (var i
= 0; i
< protocols
.length
; i
++) {
747 proto
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
749 if (!protocols
[i
].isVirtual()) {
750 bridge
.depends({ proto
: protocols
[i
].getProtocol() });
751 ifname_single
.depends({ type
: '', proto
: protocols
[i
].getProtocol() });
752 ifname_multi
.depends({ type
: 'bridge', proto
: protocols
[i
].getProtocol() });
756 m2
.render().then(L
.bind(function(nodes
) {
757 ui
.showModal(_('Add new interface...'), [
759 E('div', { 'class': 'right' }, [
762 'click': ui
.hideModal
763 }, _('Cancel')), ' ',
765 'class': 'cbi-button cbi-button-positive important',
766 'click': ui
.createHandlerFn(this, function(ev
) {
767 var nameval
= name
.isValid('_new_') ? name
.formvalue('_new_') : null,
768 protoval
= proto
.isValid('_new_') ? proto
.formvalue('_new_') : null,
769 protoclass
= protoval
? network
.getProtocol(protoval
) : null;
771 if (nameval
== null || protoval
== null || nameval
== '' || protoval
== '')
774 return protoclass
.isCreateable(nameval
).then(function(checkval
) {
775 if (checkval
!= null) {
776 ui
.addNotification(null,
777 E('p', _('New interface for "%s" can not be created: %s').format(protoclass
.getI18n(), checkval
)));
782 return m
.save(function() {
783 var section_id
= uci
.add('network', 'interface', nameval
);
785 uci
.set('network', section_id
, 'proto', protoval
);
787 if (ifname_single
.isActive('_new_')) {
788 uci
.set('network', section_id
, 'ifname', ifname_single
.formvalue('_new_'));
790 else if (ifname_multi
.isActive('_new_')) {
791 uci
.set('network', section_id
, 'type', 'bridge');
792 uci
.set('network', section_id
, 'ifname', L
.toArray(ifname_multi
.formvalue('_new_')).join(' '));
794 }).then(L
.bind(m
.children
[0].renderMoreOptionsModal
, m
.children
[0], nameval
));
798 }, _('Create interface'))
802 nodes
.querySelector('[id="%s"] input[type="text"]'.format(name
.cbid('_new_'))).focus();
806 s
.handleRemove = function(section_id
, ev
) {
807 return network
.deleteNetwork(section_id
).then(L
.bind(function(section_id
, ev
) {
808 return form
.GridSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
809 }, this, section_id
, ev
));
812 o
= s
.option(form
.DummyValue
, '_ifacebox');
814 o
.textvalue = function(section_id
) {
815 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
816 zone
= net
? this.section
.zones
.filter(function(z
) { return !!z
.getNetworks().filter(function(n
) { return n
== section_id
})[0] })[0] : null;
821 var node
= E('div', { 'class': 'ifacebox' }, [
823 'class': 'ifacebox-head',
824 'style': 'background-color:%s'.format(zone
? zone
.getColor() : '#EEEEEE'),
825 'title': zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned')
826 }, E('strong', net
.getName().toUpperCase())),
828 'class': 'ifacebox-body',
829 'id': '%s-ifc-devices'.format(section_id
),
830 'data-network': section_id
833 'src': L
.resource('icons/ethernet_disabled.png'),
834 'style': 'width:16px; height:16px'
836 E('br'), E('small', '?')
840 render_ifacebox_status(node
.childNodes
[1], net
);
845 o
= s
.option(form
.DummyValue
, '_ifacestat');
847 o
.textvalue = function(section_id
) {
848 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
853 var node
= E('div', { 'id': '%s-ifc-description'.format(section_id
) });
855 render_status(node
, net
, false);
860 o
= s
.taboption('advanced', form
.Flag
, 'delegate', _('Use builtin IPv6-management'));
862 o
.default = o
.enabled
;
864 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).'));
866 o
.render = function(option_index
, section_id
, in_table
) {
867 var protoopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'proto' })[0],
868 protoval
= protoopt
? protoopt
.cfgvalue(section_id
) : null;
870 this.default = (protoval
== 'static') ? this.enabled
: this.disabled
;
871 return this.super('render', [ option_index
, section_id
, in_table
]);
875 s
= m
.section(form
.TypedSection
, 'globals', _('Global network options'));
879 o
= s
.option(form
.Value
, 'ula_prefix', _('IPv6 ULA-Prefix'));
880 o
.datatype
= 'cidr6';
882 o
= s
.option(form
.Flag
, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
886 if (dslModemType
!= null) {
887 s
= m
.section(form
.TypedSection
, 'dsl', _('DSL'));
890 o
= s
.option(form
.ListValue
, 'annex', _('Annex'));
891 o
.value('a', _('Annex A + L + M (all)'));
892 o
.value('b', _('Annex B (all)'));
893 o
.value('j', _('Annex J (all)'));
894 o
.value('m', _('Annex M (all)'));
895 o
.value('bdmt', _('Annex B G.992.1'));
896 o
.value('b2', _('Annex B G.992.3'));
897 o
.value('b2p', _('Annex B G.992.5'));
898 o
.value('at1', _('ANSI T1.413'));
899 o
.value('admt', _('Annex A G.992.1'));
900 o
.value('alite', _('Annex A G.992.2'));
901 o
.value('a2', _('Annex A G.992.3'));
902 o
.value('a2p', _('Annex A G.992.5'));
903 o
.value('l', _('Annex L G.992.3 POTS 1'));
904 o
.value('m2', _('Annex M G.992.3'));
905 o
.value('m2p', _('Annex M G.992.5'));
907 o
= s
.option(form
.ListValue
, 'tone', _('Tone'));
908 o
.value('', _('auto'));
909 o
.value('a', _('A43C + J43 + A43'));
910 o
.value('av', _('A43C + J43 + A43 + V43'));
911 o
.value('b', _('B43 + B43C'));
912 o
.value('bv', _('B43 + B43C + V43'));
914 if (dslModemType
== 'vdsl') {
915 o
= s
.option(form
.ListValue
, 'xfer_mode', _('Encapsulation mode'));
916 o
.value('', _('auto'));
917 o
.value('atm', _('ATM (Asynchronous Transfer Mode)'));
918 o
.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
920 o
= s
.option(form
.ListValue
, 'line_mode', _('DSL line mode'));
921 o
.value('', _('auto'));
922 o
.value('adsl', _('ADSL'));
923 o
.value('vdsl', _('VDSL'));
925 o
= s
.option(form
.ListValue
, 'ds_snr_offset', _('Downstream SNR offset'));
928 for (var i
= -100; i
<= 100; i
+= 5)
929 o
.value(i
, _('%.1f dB').format(i
/ 10));
932 s
.option(form
.Value
, 'firmware', _('Firmware File'));
936 // Show ATM bridge section if we have the capabilities
937 if (L
.hasSystemFeature('br2684ctl')) {
938 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.'));
942 s
.addbtntitle
= _('Add ATM Bridge');
944 s
.handleAdd = function(ev
) {
945 var sections
= uci
.sections('network', 'atm-bridge'),
948 for (var i
= 0; i
< sections
.length
; i
++) {
949 var unit
= +sections
[i
].unit
;
951 if (!isNaN(unit
) && unit
> max_unit
)
955 return this.map
.save(function() {
956 var sid
= uci
.add('network', 'atm-bridge');
958 uci
.set('network', sid
, 'unit', max_unit
+ 1);
959 uci
.set('network', sid
, 'atmdev', 0);
960 uci
.set('network', sid
, 'encaps', 'llc');
961 uci
.set('network', sid
, 'payload', 'bridged');
962 uci
.set('network', sid
, 'vci', 35);
963 uci
.set('network', sid
, 'vpi', 8);
967 s
.tab('general', _('General Setup'));
968 s
.tab('advanced', _('Advanced Settings'));
970 o
= s
.taboption('general', form
.Value
, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
971 s
.taboption('general', form
.Value
, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
973 o
= s
.taboption('general', form
.ListValue
, 'encaps', _('Encapsulation mode'));
974 o
.value('llc', _('LLC'));
975 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'));
980 o
= s
.taboption('advanced', form
.ListValue
, 'payload', _('Forwarding mode'));
981 o
.value('bridged', _('bridged'));
982 o
.value('routed', _('routed'));
986 return m
.render().then(L
.bind(function(m
, nodes
) {
987 poll
.add(L
.bind(function() {
988 var section_ids
= m
.children
[0].cfgsections(),
991 for (var i
= 0; i
< section_ids
.length
; i
++) {
992 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
993 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
994 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
995 btn2
= row
.querySelector('.cbi-section-actions .down');
997 if (dsc
.getAttribute('reconnect') == '') {
998 dsc
.setAttribute('reconnect', '1');
999 tasks
.push(fs
.exec('/sbin/ifup', [section_ids
[i
]]).catch(function(e
) {
1000 ui
.addNotification(null, E('p', e
.message
));
1003 else if (dsc
.getAttribute('disconnect') == '') {
1004 dsc
.setAttribute('disconnect', '1');
1005 tasks
.push(fs
.exec('/sbin/ifdown', [section_ids
[i
]]).catch(function(e
) {
1006 ui
.addNotification(null, E('p', e
.message
));
1009 else if (dsc
.getAttribute('reconnect') == '1') {
1010 dsc
.removeAttribute('reconnect');
1011 btn1
.classList
.remove('spinning');
1012 btn1
.disabled
= false;
1014 else if (dsc
.getAttribute('disconnect') == '1') {
1015 dsc
.removeAttribute('disconnect');
1016 btn2
.classList
.remove('spinning');
1017 btn2
.disabled
= false;
1021 return Promise
.all(tasks
)
1022 .then(L
.bind(network
.getNetworks
, network
))
1023 .then(L
.bind(this.poll_status
, this, nodes
));