'use strict'; 'require view'; 'require dom'; 'require poll'; 'require fs'; 'require ui'; 'require uci'; 'require form'; 'require network'; 'require firewall'; 'require tools.widgets as widgets'; var isReadonlyView = !L.hasViewPermission() || null; function count_changes(section_id) { var changes = ui.changes.changes, n = 0; if (!L.isObject(changes)) return n; if (Array.isArray(changes.network)) for (var i = 0; i < changes.network.length; i++) n += (changes.network[i][1] == section_id); if (Array.isArray(changes.dhcp)) for (var i = 0; i < changes.dhcp.length; i++) n += (changes.dhcp[i][1] == section_id); return n; } function render_iface(dev, alias) { var type = dev ? dev.getType() : 'ethernet', up = dev ? dev.isUp() : false; return E('span', { class: 'cbi-tooltip-container' }, [ E('img', { 'class' : 'middle', 'src': L.resource('icons/%s%s.png').format( alias ? 'alias' : type, up ? '' : '_disabled') }), E('span', { 'class': 'cbi-tooltip ifacebadge large' }, [ E('img', { 'src': L.resource('icons/%s%s.png').format( type, up ? '' : '_disabled') }), L.itemlist(E('span', { 'class': 'left' }), [ _('Type'), dev ? dev.getTypeI18n() : null, _('Device'), dev ? dev.getName() : _('Not present'), _('Connected'), up ? _('yes') : _('no'), _('MAC'), dev ? dev.getMAC() : null, _('RX'), dev ? '%.2mB (%d %s)'.format(dev.getRXBytes(), dev.getRXPackets(), _('Pkts.')) : null, _('TX'), dev ? '%.2mB (%d %s)'.format(dev.getTXBytes(), dev.getTXPackets(), _('Pkts.')) : null ]) ]) ]); } function render_status(node, ifc, with_device) { var desc = null, c = []; if (ifc.isDynamic()) desc = _('Virtual dynamic interface'); else if (ifc.isAlias()) desc = _('Alias Interface'); else if (!uci.get('network', ifc.getName())) return L.itemlist(node, [ null, E('em', _('Interface is marked for deletion')) ]); var i18n = ifc.getI18n(); if (i18n) desc = desc ? '%s (%s)'.format(desc, i18n) : i18n; var changecount = with_device ? 0 : count_changes(ifc.getName()), ipaddrs = changecount ? [] : ifc.getIPAddrs(), ip6addrs = changecount ? [] : ifc.getIP6Addrs(), errors = ifc.getErrors(), maindev = ifc.getL3Device() || ifc.getDevice(), macaddr = maindev ? maindev.getMAC() : null; return L.itemlist(node, [ _('Protocol'), with_device ? null : (desc || '?'), _('Device'), with_device ? (maindev ? maindev.getShortName() : E('em', _('Not present'))) : null, _('Uptime'), (!changecount && ifc.isUp()) ? '%t'.format(ifc.getUptime()) : null, _('MAC'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && macaddr) ? macaddr : null, _('RX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getRXBytes(), maindev.getRXPackets(), _('Pkts.')) : null, _('TX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getTXBytes(), maindev.getTXPackets(), _('Pkts.')) : null, _('IPv4'), ipaddrs[0], _('IPv4'), ipaddrs[1], _('IPv4'), ipaddrs[2], _('IPv4'), ipaddrs[3], _('IPv4'), ipaddrs[4], _('IPv6'), ip6addrs[0], _('IPv6'), ip6addrs[1], _('IPv6'), ip6addrs[2], _('IPv6'), ip6addrs[3], _('IPv6'), ip6addrs[4], _('IPv6'), ip6addrs[5], _('IPv6'), ip6addrs[6], _('IPv6'), ip6addrs[7], _('IPv6'), ip6addrs[8], _('IPv6'), ip6addrs[9], _('IPv6-PD'), changecount ? null : ifc.getIP6Prefix(), _('Information'), with_device ? null : (ifc.get('auto') != '0' ? null : _('Not started on boot')), _('Error'), errors ? errors[0] : null, _('Error'), errors ? errors[1] : null, _('Error'), errors ? errors[2] : null, _('Error'), errors ? errors[3] : null, _('Error'), errors ? errors[4] : null, null, changecount ? E('a', { href: '#', click: L.bind(ui.changes.displayChanges, ui.changes) }, _('Interface has %d pending changes').format(changecount)) : null ]); } function render_modal_status(node, ifc) { var dev = ifc ? (ifc.getDevice() || ifc.getL3Device() || ifc.getL3Device()) : null; dom.content(node, [ E('img', { 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'), 'title': dev ? dev.getTypeI18n() : _('Not present') }), ifc ? render_status(E('span'), ifc, true) : E('em', _('Interface not present or not connected yet.')) ]); return node; } function render_ifacebox_status(node, ifc) { var dev = ifc.getL3Device() || ifc.getDevice(), subdevs = ifc.getDevices(), c = [ render_iface(dev, ifc.isAlias()) ]; if (subdevs && subdevs.length) { var sifs = [ ' (' ]; for (var j = 0; j < subdevs.length; j++) sifs.push(render_iface(subdevs[j])); sifs.push(')'); c.push(E('span', {}, sifs)); } c.push(E('br')); c.push(E('small', {}, ifc.isAlias() ? _('Alias of "%s"').format(ifc.isAlias()) : (dev ? dev.getName() : E('em', _('Not present'))))); dom.content(node, c); return firewall.getZoneByNetwork(ifc.getName()).then(L.bind(function(zone) { this.style.backgroundColor = zone ? zone.getColor() : '#EEEEEE'; this.title = zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned'); }, node.previousElementSibling)); } function iface_updown(up, id, ev, force) { var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)), dsc = row.querySelector('[data-name="_ifacestat"] > div'), btns = row.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down'); btns[+!up].blur(); btns[+!up].classList.add('spinning'); btns[0].disabled = true; btns[1].disabled = true; if (!up) { L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) { var info = null; try { info = JSON.parse(res); } catch(e) {} if (L.isObject(info) && Array.isArray(info.inbound_interfaces) && info.inbound_interfaces.filter(function(i) { return i == id })[0]) { ui.showModal(_('Confirm disconnect'), [ 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)), E('div', { 'class': 'right' }, [ E('button', { 'class': 'cbi-button cbi-button-neutral', 'click': function(ev) { btns[1].classList.remove('spinning'); btns[1].disabled = false; btns[0].disabled = false; ui.hideModal(); } }, _('Cancel')), ' ', E('button', { 'class': 'cbi-button cbi-button-negative important', 'click': function(ev) { dsc.setAttribute('disconnect', ''); dom.content(dsc, E('em', _('Interface is shutting down...'))); ui.hideModal(); } }, _('Disconnect')) ]) ]); } else { dsc.setAttribute('disconnect', ''); dom.content(dsc, E('em', _('Interface is shutting down...'))); } }); } else { dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : ''); dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...'))); } } function get_netmask(s, use_cfgvalue) { var readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue', addropt = s.children.filter(function(o) { return o.option == 'ipaddr'})[0], addrvals = addropt ? L.toArray(addropt[readfn](s.section)) : [], maskopt = s.children.filter(function(o) { return o.option == 'netmask'})[0], maskval = maskopt ? maskopt[readfn](s.section) : null, firstsubnet = maskval ? addrvals[0] + '/' + maskval : addrvals.filter(function(a) { return a.indexOf('/') > 0 })[0]; if (firstsubnet == null) return null; var mask = firstsubnet.split('/')[1]; if (!isNaN(mask)) mask = network.prefixToMask(+mask); return mask; } return view.extend({ poll_status: function(map, networks) { var resolveZone = null; for (var i = 0; i < networks.length; i++) { var ifc = networks[i], row = map.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc.getName())); if (row == null) continue; var dsc = row.querySelector('[data-name="_ifacestat"] > div'), box = row.querySelector('[data-name="_ifacebox"] .ifacebox-body'), btn1 = row.querySelector('.cbi-section-actions .reconnect'), btn2 = row.querySelector('.cbi-section-actions .down'), stat = document.querySelector('[id="%s-ifc-status"]'.format(ifc.getName())), resolveZone = render_ifacebox_status(box, ifc), disabled = ifc ? !ifc.isUp() : true, dynamic = ifc ? ifc.isDynamic() : false; if (dsc.hasAttribute('reconnect')) { dom.content(dsc, E('em', _('Interface is starting...'))); } else if (dsc.hasAttribute('disconnect')) { dom.content(dsc, E('em', _('Interface is stopping...'))); } else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) { render_status(dsc, ifc, false); } else if (!ifc.getProtocol()) { var e = map.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc.getName())); if (e) e.disabled = true; var link = L.url('admin/system/opkg') + '?query=luci-proto'; dom.content(dsc, [ E('em', _('Unsupported protocol type.')), E('br'), E('a', { href: link }, _('Install protocol extensions...')) ]); } else { dom.content(dsc, E('em', _('Interface not present or not connected yet.'))); } if (stat) { var dev = ifc.getDevice(); dom.content(stat, [ E('img', { 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'), 'title': dev ? dev.getTypeI18n() : _('Not present') }), render_status(E('span'), ifc, true) ]); } btn1.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic; btn2.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled; } return Promise.all([ resolveZone, network.flushCache() ]); }, load: function() { return Promise.all([ network.getDSLModemType(), uci.changes() ]); }, render: function(data) { var dslModemType = data[0], m, s, o; m = new form.Map('network'); m.tabbed = true; m.chain('dhcp'); s = m.section(form.GridSection, 'interface', _('Interfaces')); s.anonymous = true; s.addremove = true; s.addbtntitle = _('Add new interface...'); s.load = function() { return Promise.all([ network.getNetworks(), firewall.getZones() ]).then(L.bind(function(data) { this.networks = data[0]; this.zones = data[1]; }, this)); }; s.tab('general', _('General Settings')); s.tab('advanced', _('Advanced Settings')); s.tab('physical', _('Physical Settings')); s.tab('firewall', _('Firewall Settings')); s.tab('dhcp', _('DHCP Server')); s.cfgsections = function() { return this.networks.map(function(n) { return n.getName() }) .filter(function(n) { return n != 'loopback' }); }; s.modaltitle = function(section_id) { return _('Interfaces') + ' » ' + section_id.toUpperCase(); }; s.renderRowActions = function(section_id) { var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]), net = this.networks.filter(function(n) { return n.getName() == section_id })[0], disabled = net ? !net.isUp() : true, dynamic = net ? net.isDynamic() : false; dom.content(tdEl.lastChild, [ E('button', { 'class': 'cbi-button cbi-button-neutral reconnect', 'click': iface_updown.bind(this, true, section_id), 'title': _('Reconnect this interface'), 'disabled': dynamic ? 'disabled' : null }, _('Restart')), E('button', { 'class': 'cbi-button cbi-button-neutral down', 'click': iface_updown.bind(this, false, section_id), 'title': _('Shutdown this interface'), 'disabled': (dynamic || disabled) ? 'disabled' : null }, _('Stop')), tdEl.lastChild.firstChild, tdEl.lastChild.lastChild ]); if (!dynamic && net && !uci.get('network', net.getName())) { tdEl.lastChild.childNodes[0].disabled = true; tdEl.lastChild.childNodes[2].disabled = true; tdEl.lastChild.childNodes[3].disabled = true; } return tdEl; }; s.addModalOptions = function(s) { var protoval = uci.get('network', s.section, 'proto'), protoclass = protoval ? network.getProtocol(protoval) : null, o, ifname_single, ifname_multi, ifname_master, proto_select, proto_switch, type, stp, igmp, ss, so; if (!protoval) return; return network.getNetwork(s.section).then(L.bind(function(ifc) { var protocols = network.getProtocols(); protocols.sort(function(a, b) { return a.getProtocol() > b.getProtocol(); }); o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status')); o.modalonly = true; o.cfgvalue = L.bind(function(section_id) { var net = this.networks.filter(function(n) { return n.getName() == section_id })[0]; return render_modal_status(E('div', { 'id': '%s-ifc-status'.format(section_id), 'class': 'ifacebadge large' }), net); }, this); o.write = function() {}; o = s.taboption('general', form.Value, 'label', _('Label')); o.modalonly = true; o.optional = true; proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol')); proto_select.modalonly = true; proto_switch = s.taboption('general', form.Button, '_switch_proto'); proto_switch.modalonly = true; proto_switch.title = _('Really switch protocol?'); proto_switch.inputtitle = _('Switch protocol'); proto_switch.inputstyle = 'apply'; proto_switch.onclick = L.bind(function(ev) { s.map.save() .then(L.bind(m.load, m)) .then(L.bind(m.render, m)) .then(L.bind(this.renderMoreOptionsModal, this, s.section)); }, this); o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot')); o.modalonly = true; o.default = o.enabled; type = s.taboption('physical', form.ListValue, 'type', _('Type')); type.value('',_('Normal')); type.value('bridge',_('Bridge')); type.value('macvlan',_('MacVLAN')); type.write = type.remove = function(section_id, value) { var protocol = network.getProtocol(proto_select.formvalue(section_id)), ifnameopt = this.section.children.filter(function(o) { if (value == 'bridge') return o.option == 'ifname_multi'; else if (value == 'macvlan') return o.option == 'ifname_master'; else return o.option == 'ifname_single'; })[0]; if (!protocol.isVirtual() && !this.isActive(section_id)) return; var old_ifnames = [], devs = ifc.getDevices() || L.toArray(ifc.getDevice()); for (var i = 0; i < devs.length; i++) old_ifnames.push(devs[i].getName()); var new_ifnames = L.toArray(ifnameopt.formvalue(section_id)); if (!value) new_ifnames.length = Math.max(new_ifnames.length, 1); old_ifnames.sort(); new_ifnames.sort(); for (var i = 0; i < Math.max(old_ifnames.length, new_ifnames.length); i++) { if (old_ifnames[i] != new_ifnames[i]) { // backup_ifnames() for (var j = 0; j < old_ifnames.length; j++) ifc.deleteDevice(old_ifnames[j]); for (var j = 0; j < new_ifnames.length; j++) ifc.addDevice(new_ifnames[j]); break; } } if (value) uci.set('network', section_id, 'type', value); else uci.unset('network', section_id, 'type'); }; stp = s.taboption('physical', form.Flag, 'stp', _('Enable STP'), _('Enables the Spanning Tree Protocol on this bridge')); igmp = s.taboption('physical', form.Flag, 'igmp_snooping', _('Enable IGMP snooping'), _('Enables IGMP snooping on this bridge')); ifname_master = s.taboption('physical', widgets.DeviceSelect, 'ifname_master', _('Base interface')); ifname_master.nobridges = true; ifname_master.noaliases = true; ifname_master.optional = false; ifname_master.modalonly = true; ifname_master.network = ifc.getName(); //ifname_master.write = ifname_master.remove = function() {}; ifname_master.ucioption = 'masterintf'; //ifname_master.cfgvalue = function(section_id) { // return uci.get('network', section_id, 'masterintf'); //}; //ifname_master.write = function(section_id, value) { // uci.set('network', section_id, 'masterintf', value); //}; ifname_single = s.taboption('physical', widgets.DeviceSelect, 'ifname_single', _('Interface')); ifname_single.nobridges = ifc.isBridge(); ifname_single.noaliases = false; ifname_single.optional = false; ifname_single.network = ifc.getName(); ifname_single.write = ifname_single.remove = function() {}; ifname_multi = s.taboption('physical', widgets.DeviceSelect, 'ifname_multi', _('Interface')); ifname_multi.nobridges = ifc.isBridge(); ifname_multi.noaliases = true; ifname_multi.multiple = true; ifname_multi.optional = true; ifname_multi.network = ifc.getName(); ifname_multi.display_size = 6; ifname_multi.write = ifname_multi.remove = function() {}; ifname_single.cfgvalue = ifname_multi.cfgvalue = function(section_id) { var devs = ifc.getDevices() || L.toArray(ifc.getDevice()), ifnames = []; for (var i = 0; i < devs.length; i++) ifnames.push(devs[i].getName()); return ifnames; }; if (L.hasSystemFeature('firewall')) { o = s.taboption('firewall', widgets.ZoneSelect, '_zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select unspecified to remove the interface from the associated zone or fill out the custom field to define a new zone and attach the interface to it.')); o.network = ifc.getName(); o.optional = true; o.cfgvalue = function(section_id) { return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) { return (zone != null ? zone.getName() : null); }); }; o.write = o.remove = function(section_id, value) { return Promise.all([ firewall.getZoneByNetwork(ifc.getName()), (value != null) ? firewall.getZone(value) : null ]).then(function(data) { var old_zone = data[0], new_zone = data[1]; if (old_zone == null && new_zone == null && (value == null || value == '')) return; if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName()) return; if (old_zone != null) old_zone.deleteNetwork(ifc.getName()); if (new_zone != null) new_zone.addNetwork(ifc.getName()); else if (value != null) return firewall.addZone(value).then(function(new_zone) { new_zone.addNetwork(ifc.getName()); }); }); }; } for (var i = 0; i < protocols.length; i++) { proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n()); if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto')) proto_switch.depends('proto', protocols[i].getProtocol()); if (!protocols[i].isVirtual()) { type.depends('proto', protocols[i].getProtocol()); stp.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); igmp.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); ifname_single.depends({ type: '', proto: protocols[i].getProtocol() }); ifname_master.depends({ type: 'macvlan', proto: protocols[i].getProtocol() }); ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); } } o = s.taboption('advanced', form.ListValue, 'multipath', _('Multipath setting'), _('Only one interface must be set as Master.')); o.value('on',_('Enabled')); o.value('off',_('Disabled')); o.value('master',_('Master')); o.value('backup',_('Backup')); o.default = 'disabled'; if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) { o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp'); o.depends('proto', 'static'); ss = o.subsection; ss.uciconfig = 'dhcp'; ss.addremove = false; ss.anonymous = true; ss.tab('general', _('General Setup')); ss.tab('advanced', _('Advanced Settings')); ss.tab('ipv6', _('IPv6 Settings')); ss.filter = function(section_id) { return (uci.get('dhcp', section_id, 'interface') == ifc.getName()); }; ss.renderSectionPlaceholder = function() { return E('div', { 'class': 'cbi-section-create' }, [ E('p', _('No DHCP Server configured for this interface') + '   '), E('button', { 'class': 'cbi-button cbi-button-add', 'title': _('Setup DHCP Server'), 'click': ui.createHandlerFn(this, function(section_id, ev) { this.map.save(function() { uci.add('dhcp', 'dhcp', section_id); uci.set('dhcp', section_id, 'interface', section_id); uci.set('dhcp', section_id, 'start', 100); uci.set('dhcp', section_id, 'limit', 150); uci.set('dhcp', section_id, 'leasetime', '12h'); }); }, ifc.getName()) }, _('Setup DHCP Server')) ]); }; ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable DHCP for this interface.')); so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.')); so.optional = true; so.datatype = 'or(uinteger,ip4addr("nomask"))'; so.default = '100'; so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.')); so.optional = true; so.datatype = 'uinteger'; so.default = '150'; so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (2m).')); so.optional = true; so.default = '12h'; so = ss.taboption('advanced', form.Flag, 'dynamicdhcp', _('Dynamic DHCP'), _('Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.')); so.default = so.enabled; ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.')); // XXX: is this actually useful? //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.')); so = ss.taboption('advanced', form.Value, 'netmask', _('IPv4-Netmask'), _('Override the netmask sent to clients. Normally it is calculated from the subnet that is served.')); so.optional = true; so.datatype = 'ip4addr'; so.render = function(option_index, section_id, in_table) { this.placeholder = get_netmask(s, true); return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]); }; so.validate = function(section_id, value) { var node = this.map.findElement('id', this.cbid(section_id)); if (node) node.querySelector('input').setAttribute('placeholder', get_netmask(s, false)); return form.Value.prototype.validate.apply(this, [ section_id, value ]); }; ss.taboption('advanced', form.DynamicList, 'dhcp_option', _('DHCP-Options'), _('Define additional DHCP options, for example "6,192.168.2.1,192.168.2.2" which advertises different DNS servers to clients.')); for (var i = 0; i < ss.children.length; i++) if (ss.children[i].option != 'ignore') ss.children[i].depends('ignore', '0'); so = ss.taboption('ipv6', form.ListValue, 'ra', _('Router Advertisement-Service')); so.value('', _('disabled')); so.value('server', _('server mode')); so.value('relay', _('relay mode')); so.value('hybrid', _('hybrid mode')); so = ss.taboption('ipv6', form.ListValue, 'dhcpv6', _('DHCPv6-Service')); so.value('', _('disabled')); so.value('server', _('server mode')); so.value('relay', _('relay mode')); so.value('hybrid', _('hybrid mode')); so = ss.taboption('ipv6', form.ListValue, 'ndp', _('NDP-Proxy')); so.value('', _('disabled')); so.value('relay', _('relay mode')); so.value('hybrid', _('hybrid mode')); so = ss.taboption('ipv6', form.Flag , 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.')); so.depends('dhcpv6', 'relay'); so.depends('dhcpv6', 'hybrid'); so = ss.taboption('ipv6', form.ListValue, 'ra_management', _('DHCPv6-Mode'), _('Default is stateless + stateful')); so.value('0', _('stateless')); so.value('1', _('stateless + stateful')); so.value('2', _('stateful-only')); so.depends('dhcpv6', 'server'); so.depends('dhcpv6', 'hybrid'); so.default = '1'; so = ss.taboption('ipv6', form.Flag, 'ra_default', _('Always announce default router'), _('Announce as default router even if no public prefix is available.')); so.depends('ra', 'server'); so.depends('ra', 'hybrid'); ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced DNS servers')); ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains')); } ifc.renderFormOptions(s); for (var i = 0; i < s.children.length; i++) { o = s.children[i]; switch (o.option) { case 'proto': case 'delegate': case 'auto': case 'type': case 'stp': case 'igmp_snooping': case 'ifname_single': case 'ifname_multi': case 'ifname_master': case '_dhcp': case '_zone': case '_switch_proto': case '_ifacestat_modal': continue; default: if (o.deps.length) for (var j = 0; j < o.deps.length; j++) o.deps[j].proto = protoval; else o.depends('proto', protoval); } } }, this)); }; s.handleAdd = function(ev) { var m2 = new form.Map('network'), s2 = m2.section(form.NamedSection, '_new_'), protocols = network.getProtocols(), proto, name, type, ifname_single, ifname_multi, ifname_master; protocols.sort(function(a, b) { return a.getProtocol() > b.getProtocol(); }); s2.render = function() { return Promise.all([ {}, this.renderUCISection('_new_') ]).then(this.renderContents.bind(this)); }; name = s2.option(form.Value, 'name', _('Name')); name.rmempty = false; name.datatype = 'uciname'; name.placeholder = _('New interface name…'); name.validate = function(section_id, value) { if (uci.get('network', value) != null) return _('The interface name is already used'); var pr = network.getProtocol(proto.formvalue(section_id), value), ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value); if (value.length > 15) return _('The interface name is too long'); return true; }; proto = s2.option(form.ListValue, 'proto', _('Protocol')); proto.validate = name.validate; type = s2.option(form.ListValue, 'type', _('Interface type')); type.value('',_('Normal')); type.value('bridge',_('Bridge')); type.value('macvlan',_('MacVLAN')); ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface')); ifname_single.noaliases = false; ifname_single.optional = false; ifname_master = s2.option(widgets.DeviceSelect, 'ifname_master', _('Base interface')); ifname_master.noaliases = false; ifname_master.optional = false; ifname_multi = s2.option(widgets.DeviceSelect, 'ifname_multi', _('Interface')); ifname_multi.nobridges = true; ifname_multi.noaliases = true; ifname_multi.multiple = true; ifname_multi.optional = true; ifname_multi.display_size = 6; for (var i = 0; i < protocols.length; i++) { proto.value(protocols[i].getProtocol(), protocols[i].getI18n()); if (!protocols[i].isVirtual()) { type.depends({ proto: protocols[i].getProtocol() }); ifname_single.depends({ type: '', proto: protocols[i].getProtocol() }); ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); ifname_master.depends({ type: 'macvlan', proto: protocols[i].getProtocol() }); } } m2.render().then(L.bind(function(nodes) { ui.showModal(_('Add new interface...'), [ nodes, E('div', { 'class': 'right' }, [ E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')), ' ', E('button', { 'class': 'cbi-button cbi-button-positive important', 'click': ui.createHandlerFn(this, function(ev) { var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null, protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null, protoclass = protoval ? network.getProtocol(protoval) : null; if (nameval == null || protoval == null || nameval == '' || protoval == '') return; return protoclass.isCreateable(nameval).then(function(checkval) { if (checkval != null) { ui.addNotification(null, E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval))); ui.hideModal(); return; } return m.save(function() { var section_id = uci.add('network', 'interface', nameval); uci.set('network', section_id, 'proto', protoval); if (ifname_single.isActive('_new_')) { uci.set('network', section_id, 'ifname', ifname_single.formvalue('_new_')); } else if (ifname_multi.isActive('_new_')) { uci.set('network', section_id, 'type', 'bridge'); uci.set('network', section_id, 'ifname', L.toArray(ifname_multi.formvalue('_new_')).join(' ')); } else if (ifname_master.isActive('_new_')) { uci.set('network', section_id, 'type', 'macvlan'); uci.set('network', section_id, 'ifname', section_id); uci.set('network', section_id, 'masterintf', L.toArray(ifname_multi.formvalue('_new_')).join(' ')); } }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval)); }); }) }, _('Create interface')) ]) ], 'cbi-modal'); nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus(); }, this)); }; s.handleRemove = function(section_id, ev) { return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) { return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]); }, this, section_id, ev)); }; o = s.option(form.DummyValue, '_ifacebox'); o.modalonly = false; o.textvalue = function(section_id) { var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0], zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null; if (!net) return; var node = E('div', { 'class': 'ifacebox' }, [ E('div', { 'class': 'ifacebox-head', 'style': 'background-color:%s'.format(zone ? zone.getColor() : '#EEEEEE'), 'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned') }, E('strong', net.getName().toUpperCase())), E('div', { 'class': 'ifacebox-body', 'id': '%s-ifc-devices'.format(section_id), 'data-network': section_id }, [ E('img', { 'src': L.resource('icons/ethernet_disabled.png'), 'style': 'width:16px; height:16px' }), E('br'), E('small', '?') ]) ]); render_ifacebox_status(node.childNodes[1], net); return node; }; o = s.option(form.DummyValue, '_ifacestat'); o.modalonly = false; o.textvalue = function(section_id) { var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0]; if (!net) return; var node = E('div', { 'id': '%s-ifc-description'.format(section_id) }); render_status(node, net, false); return node; }; o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management')); o.modalonly = true; o.default = o.enabled; 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).')); o.modalonly = true; o.render = function(option_index, section_id, in_table) { var protoopt = this.section.children.filter(function(o) { return o.option == 'proto' })[0], protoval = protoopt ? protoopt.cfgvalue(section_id) : null; this.default = (protoval == 'static') ? this.enabled : this.disabled; return this.super('render', [ option_index, section_id, in_table ]); }; s = m.section(form.TypedSection, 'globals', _('Global network options')); s.addremove = false; s.anonymous = true; o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix')); o.datatype = 'cidr6'; o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.')); o.optional = true; if (dslModemType != null) { s = m.section(form.TypedSection, 'dsl', _('DSL')); s.anonymous = true; o = s.option(form.ListValue, 'annex', _('Annex')); o.value('a', _('Annex A + L + M (all)')); o.value('b', _('Annex B (all)')); o.value('j', _('Annex J (all)')); o.value('m', _('Annex M (all)')); o.value('bdmt', _('Annex B G.992.1')); o.value('b2', _('Annex B G.992.3')); o.value('b2p', _('Annex B G.992.5')); o.value('at1', _('ANSI T1.413')); o.value('admt', _('Annex A G.992.1')); o.value('alite', _('Annex A G.992.2')); o.value('a2', _('Annex A G.992.3')); o.value('a2p', _('Annex A G.992.5')); o.value('l', _('Annex L G.992.3 POTS 1')); o.value('m2', _('Annex M G.992.3')); o.value('m2p', _('Annex M G.992.5')); o = s.option(form.ListValue, 'tone', _('Tone')); o.value('', _('auto')); o.value('a', _('A43C + J43 + A43')); o.value('av', _('A43C + J43 + A43 + V43')); o.value('b', _('B43 + B43C')); o.value('bv', _('B43 + B43C + V43')); if (dslModemType == 'vdsl') { o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode')); o.value('', _('auto')); o.value('atm', _('ATM (Asynchronous Transfer Mode)')); o.value('ptm', _('PTM/EFM (Packet Transfer Mode)')); o = s.option(form.ListValue, 'line_mode', _('DSL line mode')); o.value('', _('auto')); o.value('adsl', _('ADSL')); o.value('vdsl', _('VDSL')); o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset')); o.default = '0'; for (var i = -100; i <= 100; i += 5) o.value(i, _('%.1f dB').format(i / 10)); } s.option(form.Value, 'firmware', _('Firmware File')); } // Show ATM bridge section if we have the capabilities if (L.hasSystemFeature('br2684ctl')) { 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.')); s.addremove = true; s.anonymous = true; s.addbtntitle = _('Add ATM Bridge'); s.handleAdd = function(ev) { var sections = uci.sections('network', 'atm-bridge'), max_unit = -1; for (var i = 0; i < sections.length; i++) { var unit = +sections[i].unit; if (!isNaN(unit) && unit > max_unit) max_unit = unit; } return this.map.save(function() { var sid = uci.add('network', 'atm-bridge'); uci.set('network', sid, 'unit', max_unit + 1); uci.set('network', sid, 'atmdev', 0); uci.set('network', sid, 'encaps', 'llc'); uci.set('network', sid, 'payload', 'bridged'); uci.set('network', sid, 'vci', 35); uci.set('network', sid, 'vpi', 8); }); }; s.tab('general', _('General Setup')); s.tab('advanced', _('Advanced Settings')); o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)')); s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)')); o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode')); o.value('llc', _('LLC')); o.value('vc', _('VC-Mux')); s.taboption('advanced', form.Value, 'atmdev', _('ATM device number')); s.taboption('advanced', form.Value, 'unit', _('Bridge unit number')); o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode')); o.value('bridged', _('bridged')); o.value('routed', _('routed')); } return m.render().then(L.bind(function(m, nodes) { poll.add(L.bind(function() { var section_ids = m.children[0].cfgsections(), tasks = []; for (var i = 0; i < section_ids.length; i++) { var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])), dsc = row.querySelector('[data-name="_ifacestat"] > div'), btn1 = row.querySelector('.cbi-section-actions .reconnect'), btn2 = row.querySelector('.cbi-section-actions .down'); if (dsc.getAttribute('reconnect') == '') { dsc.setAttribute('reconnect', '1'); tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) { ui.addNotification(null, E('p', e.message)); })); } else if (dsc.getAttribute('disconnect') == '') { dsc.setAttribute('disconnect', '1'); tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) { ui.addNotification(null, E('p', e.message)); })); } else if (dsc.getAttribute('reconnect') == '1') { dsc.removeAttribute('reconnect'); btn1.classList.remove('spinning'); btn1.disabled = false; } else if (dsc.getAttribute('disconnect') == '1') { dsc.removeAttribute('disconnect'); btn2.classList.remove('spinning'); btn2.disabled = false; } } return Promise.all(tasks) .then(L.bind(network.getNetworks, network)) .then(L.bind(this.poll_status, this, nodes)); }, this), 5); return nodes; }, this, m)); } });