From e4bc192012b05078eb7675e42908e0dd9d04ee88 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 20 Aug 2019 15:39:16 +0200 Subject: [PATCH] luci-mod-network: switch to client side interface configuration pages Signed-off-by: Jo-Philipp Wich --- .../resources/view/network/interfaces.js | 982 ++++++++++++++++++ .../luasrc/controller/admin/network.lua | 75 +- .../model/cbi/admin_network/iface_add.lua | 101 -- .../luasrc/model/cbi/admin_network/ifaces.lua | 563 ---------- .../model/cbi/admin_network/network.lua | 202 ---- .../view/admin_network/iface_overview.htm | 53 - 6 files changed, 1042 insertions(+), 934 deletions(-) create mode 100644 modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js delete mode 100644 modules/luci-mod-network/luasrc/model/cbi/admin_network/iface_add.lua delete mode 100644 modules/luci-mod-network/luasrc/model/cbi/admin_network/ifaces.lua delete mode 100644 modules/luci-mod-network/luasrc/model/cbi/admin_network/network.lua delete mode 100644 modules/luci-mod-network/luasrc/view/admin_network/iface_overview.htm diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js new file mode 100644 index 000000000..aac0071a8 --- /dev/null +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js @@ -0,0 +1,982 @@ +'use strict'; +'require uci'; +'require form'; +'require network'; +'require firewall'; +'require tools.widgets as widgets'; + +function count_changes(section_id) { + var changes = L.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.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(L.ui.changes.displayChanges, L.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; + + L.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'))))); + + L.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; + + dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : ''); + L.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 L.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')) { + L.dom.content(dsc, E('em', _('Interface is starting...'))); + } + else if (dsc.hasAttribute('disconnect')) { + L.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'; + L.dom.content(dsc, [ + E('em', _('Unsupported protocol type.')), E('br'), + E('a', { href: link }, _('Install protocol extensions...')) + ]); + } + else { + L.dom.content(dsc, E('em', _('Interface not present or not connected yet.'))); + } + + if (stat) { + var dev = ifc.getDevice(); + L.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 = btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic; + btn2.disabled = 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; + + L.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, 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() {}; + + 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.Flag, 'type', _('Bridge interfaces'), _('creates a bridge over specified interface(s)')); + type.modalonly = true; + type.disabled = ''; + type.enabled = 'bridge'; + type.write = type.remove = function(section_id, value) { + var protocol = network.getProtocol(proto_select.formvalue(section_id)), + ifnameopt = this.section.children.filter(function(o) { return o.option == (value ? 'ifname_multi' : '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', 'bridge'); + 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_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 create 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_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); + } + } + + 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': L.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.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 '_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, bridge, ifname_single, ifname_multi; + + 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; + + bridge = s2.option(form.Flag, 'type', _('Bridge interfaces'), _('creates a bridge over specified interface(s)')); + bridge.modalonly = true; + bridge.disabled = ''; + bridge.enabled = 'bridge'; + + ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface')); + ifname_single.noaliases = false; + ifname_single.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()) { + bridge.depends({ proto: protocols[i].getProtocol() }); + ifname_single.depends({ type: '', proto: protocols[i].getProtocol() }); + ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); + } + } + + m2.render().then(L.bind(function(nodes) { + L.ui.showModal(_('Add new interface...'), [ + nodes, + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn', + 'click': L.ui.hideModal + }, _('Cancel')), ' ', + E('button', { + 'class': 'cbi-button cbi-button-positive important', + 'click': L.ui.createHandlerFn(this, function(ev) { + var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null, + protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null; + + if (nameval == null || protoval == null || nameval == '' || protoval == '') + 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(' ')); + } + }).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)); + }; + + 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'; + + + 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) { + L.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(L.Request.post( + L.url('admin/network/iface_reconnect', section_ids[i]), + 'token=' + L.env.token, + { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } + ).catch(function() {})); + } + else if (dsc.getAttribute('disconnect') == '' || dsc.getAttribute('disconnect') == 'force') { + var force = dsc.getAttribute('disconnect'); + dsc.setAttribute('disconnect', '1'); + tasks.push(L.Request.post( + L.url('admin/network/iface_down', section_ids[i], force), + 'token=' + L.env.token, + { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } + ).then(L.bind(function(ifname, res) { + if (res.status == 409) { + L.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(ifname)), + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'click': L.ui.hideModal + }, _('Cancel')), + ' ', + E('button', { + 'class': 'cbi-button cbi-button-negative important', + 'click': function(ev) { + iface_updown(false, ifname, ev, true); + L.ui.hideModal(); + } + }, _('Disconnect')) + ]) + ]); + } + }, this, section_ids[i]), function() {})); + } + 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)); + } +}); diff --git a/modules/luci-mod-network/luasrc/controller/admin/network.lua b/modules/luci-mod-network/luasrc/controller/admin/network.lua index b20607e2e..4578d1257 100644 --- a/modules/luci-mod-network/luasrc/controller/admin/network.lua +++ b/modules/luci-mod-network/luasrc/controller/admin/network.lua @@ -76,30 +76,19 @@ function index() end - page = entry({"admin", "network", "iface_add"}, form("admin_network/iface_add"), nil) - page.leaf = true - page = entry({"admin", "network", "iface_status"}, call("iface_status"), nil) page.leaf = true page = entry({"admin", "network", "iface_reconnect"}, post("iface_reconnect"), nil) page.leaf = true - page = entry({"admin", "network", "network"}, arcombine(cbi("admin_network/network"), cbi("admin_network/ifaces")), _("Interfaces"), 10) + page = entry({"admin", "network", "iface_down"}, post("iface_down"), nil) + page.leaf = true + + page = entry({"admin", "network", "network"}, view("network/interfaces"), _("Interfaces"), 10) page.leaf = true page.subindex = true - if page.inreq then - uci:foreach("network", "interface", - function (section) - local ifc = section[".name"] - if ifc ~= "loopback" then - entry({"admin", "network", "network", ifc}, - true, ifc:upper()) - end - end) - end - if nixio.fs.access("/etc/config/dhcp") then page = node("admin", "network", "dhcp") @@ -268,6 +257,62 @@ function iface_reconnect(iface) luci.http.status(404, "No such interface") end +local function addr2dev(addr, src) + local ip = require "luci.ip" + local route = ip.route(addr, src) + if not src and route and route.src then + route = ip.route(addr, route.src:string()) + end + return route and route.dev +end + +function iface_down(iface, force) + local netmd = require "luci.model.network".init() + local peer = luci.http.getenv("REMOTE_ADDR") + local serv = luci.http.getenv("SERVER_ADDR") + + if force ~= "force" and serv and peer then + local dev = addr2dev(peer, serv) + if dev then + local nets = netmd:get_networks() + local outnet = nil + local _, net, ai + + for _, net in ipairs(nets) do + if net:contains_interface(dev) then + outnet = net + break + end + end + + if outnet:name() == iface then + luci.http.status(409, "Is inbound interface") + return + end + + local peeraddr = outnet:get("peeraddr") + for _, ai in ipairs(peeraddr and nixio.getaddrinfo(peeraddr) or {}) do + local peerdev = addr2dev(ai.address) + for _, net in ipairs(peerdev and nets or {}) do + if net:contains_interface(peerdev) and net:name() == iface then + luci.http.status(409, "Is inbound interface") + return + end + end + end + end + end + + if netmd:get_network(iface) then + luci.sys.call("env -i /sbin/ifdown %s >/dev/null 2>/dev/null" + % luci.util.shellquote(iface)) + luci.http.status(200, "Shut down") + return + end + + luci.http.status(404, "No such interface") +end + function wifi_status(devs) local s = require "luci.tools.status" local rv = { } diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/iface_add.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/iface_add.lua deleted file mode 100644 index ca66e9f36..000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/iface_add.lua +++ /dev/null @@ -1,101 +0,0 @@ --- Copyright 2009-2010 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local nw = require "luci.model.network".init() -local fw = require "luci.model.firewall".init() -local utl = require "luci.util" -local uci = require "luci.model.uci".cursor() - -m = SimpleForm("network", translate("Create Interface")) -m.redirect = luci.dispatcher.build_url("admin/network/network") -m.reset = false - -function m.on_cancel() - luci.http.redirect(luci.dispatcher.build_url("admin/network/network")) -end - -newnet = m:field(Value, "_netname", translate("Name of the new interface"), - translate("The allowed characters are: A-Z, a-z, " .. - "0-9 and _" - )) - -newnet:depends("_attach", "") -newnet.default = arg[1] and "net_" .. arg[1]:gsub("[^%w_]+", "_") -newnet.datatype = "and(uciname,maxlength(15))" - -advice = m:field(DummyValue, "d1", translate("Note: interface name length"), - translate("Maximum length of the name is 15 characters including " .. - "the automatic protocol/bridge prefix (br-, 6in4-, pppoe- etc.)" - )) - -newproto = m:field(ListValue, "_netproto", translate("Protocol of the new interface")) - -netbridge = m:field(Flag, "_bridge", translate("Create a bridge over multiple interfaces")) - - -sifname = m:field(Value, "_ifname", translate("Cover the following interface")) - -sifname.widget = "radio" -sifname.template = "cbi/network_ifacelist" -sifname.nobridges = true - - -mifname = m:field(Value, "_ifnames", translate("Cover the following interfaces")) - -mifname.widget = "checkbox" -mifname.template = "cbi/network_ifacelist" -mifname.nobridges = true - - -local _, p -for _, p in ipairs(nw:get_protocols()) do - if p:is_installed() then - newproto:value(p:proto(), p:get_i18n()) - if not p:is_virtual() then netbridge:depends("_netproto", p:proto()) end - if not p:is_floating() then - sifname:depends({ _bridge = "", _netproto = p:proto()}) - mifname:depends({ _bridge = "1", _netproto = p:proto()}) - end - end -end - -function newproto.validate(self, value, section) - local name = newnet:formvalue(section) - if not name or #name == 0 then - newnet:add_error(section, translate("No network name specified")) - elseif m:get(name) then - newnet:add_error(section, translate("The given network name is not unique")) - end - - local proto = nw:get_protocol(value) - if proto and not proto:is_floating() then - local br = (netbridge:formvalue(section) == "1") - local ifn = br and mifname:formvalue(section) or sifname:formvalue(section) - for ifn in utl.imatch(ifn) do - return value - end - return nil, translate("The selected protocol needs a device assigned") - end - return value -end - -function newproto.write(self, section, value) - local name = newnet:formvalue(section) - if name and #name > 0 then - local br = (netbridge:formvalue(section) == "1") and "bridge" or nil - local net = nw:add_network(name, { proto = value, type = br }) - if net then - local ifn - for ifn in utl.imatch( - br and mifname:formvalue(section) or sifname:formvalue(section) - ) do - net:add_interface(ifn) - end - nw:save("network") - nw:save("wireless") - end - luci.http.redirect(luci.dispatcher.build_url("admin/network/network", name)) - end -end - -return m diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/ifaces.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/ifaces.lua deleted file mode 100644 index de7b8676e..000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/ifaces.lua +++ /dev/null @@ -1,563 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2008-2011 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local ut = require "luci.util" -local pt = require "luci.tools.proto" -local nw = require "luci.model.network" -local fw = require "luci.model.firewall" - -arg[1] = arg[1] or "" - -local has_dnsmasq = fs.access("/etc/config/dhcp") -local has_firewall = fs.access("/etc/config/firewall") - -m = Map("network", translate("Interfaces") .. " - " .. arg[1]:upper(), translate("On this page you can configure the network interfaces. You can bridge several interfaces by ticking the \"bridge interfaces\" field and enter the names of several network interfaces separated by spaces. You can also use VLAN notation INTERFACE.VLANNR (e.g.: eth0.1).")) -m.redirect = luci.dispatcher.build_url("admin", "network", "network") -m:chain("wireless") -m:chain("luci") - -if has_firewall then - m:chain("firewall") -end - -nw.init(m.uci) -fw.init(m.uci) - - -local net = nw:get_network(arg[1]) - -local function set_ifstate(name, option, value) - local found = false - - m.uci:foreach("luci", "ifstate", function (s) - if s.interface == name then - m.uci:set("luci", s[".name"], option, value) - found = true - return false - end - end) - - if not found then - local sid = m.uci:add("luci", "ifstate") - m.uci:set("luci", sid, "interface", name) - m.uci:set("luci", sid, option, value) - end - - m.uci:save("luci") -end - -local function get_ifstate(name, option) - local val - - m.uci:foreach("luci", "ifstate", function (s) - if s.interface == name then - val = s[option] - return false - end - end) - - return val -end - -local function backup_ifnames(is_bridge) - if not net:is_floating() and not get_ifstate(net:name(), "ifname") then - local ifcs = net:get_interfaces() or { net:get_interface() } - if ifcs then - local _, ifn - local ifns = { } - for _, ifn in ipairs(ifcs) do - local wif = ifn:get_wifinet() - ifns[#ifns+1] = wif and wif:id() or ifn:name() - end - if #ifns > 0 then - set_ifstate(net:name(), "ifname", table.concat(ifns, " ")) - set_ifstate(net:name(), "bridge", tostring(net:is_bridge())) - end - end - end -end - - --- redirect to overview page if network does not exist anymore (e.g. after a revert) -if not net then - luci.http.redirect(luci.dispatcher.build_url("admin/network/network")) - return -end - --- protocol switch was requested, rebuild interface config and reload page -if m:formvalue("cbid.network.%s._switch" % net:name()) then - -- get new protocol - local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-" - local proto = nw:get_protocol(ptype, net:name()) - if proto then - -- backup default - backup_ifnames() - - -- if current proto is not floating and target proto is not floating, - -- then attempt to retain the ifnames - --error(net:proto() .. " > " .. proto:proto()) - if not net:is_floating() and not proto:is_floating() then - -- if old proto is a bridge and new proto not, then clip the - -- interface list to the first ifname only - if net:is_bridge() and proto:is_virtual() then - local _, ifn - local first = true - for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do - if first then - first = false - else - net:del_interface(ifn) - end - end - m:del(net:name(), "type") - end - - -- if the current proto is floating, the target proto not floating, - -- then attempt to restore ifnames from backup - elseif net:is_floating() and not proto:is_floating() then - -- if we have backup data, then re-add all orphaned interfaces - -- from it and restore the bridge choice - local br = (get_ifstate(net:name(), "bridge") == "true") - local ifn - local ifns = { } - for ifn in ut.imatch(get_ifstate(net:name(), "ifname")) do - ifn = nw:get_interface(ifn) - if ifn and not ifn:get_network() then - proto:add_interface(ifn) - if not br then - break - end - end - end - if br then - m:set(net:name(), "type", "bridge") - end - - -- in all other cases clear the ifnames - else - local _, ifc - for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do - net:del_interface(ifc) - end - m:del(net:name(), "type") - end - - -- clear options - local k, v - for k, v in pairs(m:get(net:name())) do - if k:sub(1,1) ~= "." and - k ~= "type" and - k ~= "ifname" - then - m:del(net:name(), k) - end - end - - -- set proto - m:set(net:name(), "proto", proto:proto()) - m.uci:save("network") - m.uci:save("wireless") - - -- reload page - luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1])) - return - end -end - --- dhcp setup was requested, create section and reload page -if m:formvalue("cbid.dhcp._enable._enable") then - m.uci:section("dhcp", "dhcp", arg[1], { - interface = arg[1], - start = "100", - limit = "150", - leasetime = "12h" - }) - - m.uci:save("dhcp") - luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1])) - return -end - -local ifc = net:get_interface() - -s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration")) -s.addremove = false - -s:tab("general", translate("General Setup")) -s:tab("advanced", translate("Advanced Settings")) -s:tab("physical", translate("Physical Settings")) - -if has_firewall then - s:tab("firewall", translate("Firewall Settings")) -end - - -st = s:taboption("general", DummyValue, "__status", translate("Status")) - -local function set_status() - -- if current network is empty, print a warning - if not net:is_floating() and net:is_empty() then - st.template = "cbi/dvalue" - st.network = nil - st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab") - else - st.template = "admin_network/iface_status" - st.network = arg[1] - st.value = nil - end -end - -m.on_init = set_status -m.on_after_save = set_status - - -p = s:taboption("general", ListValue, "proto", translate("Protocol")) -p.default = net:proto() - - -if not net:is_installed() then - p_install = s:taboption("general", Button, "_install") - p_install.title = translate("Protocol support is not installed") - p_install.inputtitle = translate("Install package %q" % net:opkg_package()) - p_install.inputstyle = "apply" - p_install:depends("proto", net:proto()) - - function p_install.write() - return luci.http.redirect( - luci.dispatcher.build_url("admin/system/opkg") .. - "?query=%s" % net:opkg_package() - ) - end -end - - -p_switch = s:taboption("general", Button, "_switch") -p_switch.title = translate("Really switch protocol?") -p_switch.inputtitle = translate("Switch protocol") -p_switch.inputstyle = "apply" - -local _, pr -for _, pr in ipairs(nw:get_protocols()) do - p:value(pr:proto(), pr:get_i18n()) - if pr:proto() ~= net:proto() then - p_switch:depends("proto", pr:proto()) - end -end - - -auto = s:taboption("general", Flag, "auto", translate("Bring up on boot")) -auto.default = (net:proto() == "none") and auto.disabled or auto.enabled - -delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management")) -delegate.default = delegate.enabled - -force_link = s:taboption("advanced", Flag, "force_link", - translate("Force link"), - translate("Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers).")) - -force_link.default = (net:proto() == "static") and force_link.enabled or force_link.disabled - - -if not net:is_virtual() then - br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)")) - br.enabled = "bridge" - br.rmempty = true - br:depends("proto", "static") - br:depends("proto", "dhcp") - br:depends("proto", "none") - - stp = s:taboption("physical", Flag, "stp", translate("Enable STP"), - translate("Enables the Spanning Tree Protocol on this bridge")) - stp:depends("type", "bridge") - stp.rmempty = true - - igmp = s:taboption("physical", Flag, "igmp_snooping", translate("Enable IGMP snooping"), - translate("Enables IGMP snooping on this bridge")) - igmp:depends("type", "bridge") - igmp.rmempty = true -end - - -if not net:is_floating() then - ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface")) - ifname_single.template = "cbi/network_ifacelist" - ifname_single.widget = "radio" - ifname_single.nobridges = net:is_bridge() - ifname_single.noaliases = false - ifname_single.rmempty = false - ifname_single.network = arg[1] - ifname_single:depends("type", "") - - function ifname_single.cfgvalue(self, s) - -- let the template figure out the related ifaces through the network model - return nil - end - - function ifname_single.write(self, s, val) - local _, i - local new_ifs = { } - local old_ifs = { } - - local alias = net:is_alias() - - if alias then - old_ifs[1] = '@' .. alias - else - for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do - old_ifs[#old_ifs+1] = i:name() - end - end - - for i in ut.imatch(val) do - new_ifs[#new_ifs+1] = i - - -- if this is not a bridge, only assign first interface - if self.option == "ifname_single" then - break - end - end - - table.sort(old_ifs) - table.sort(new_ifs) - - for i = 1, math.max(#old_ifs, #new_ifs) do - if old_ifs[i] ~= new_ifs[i] then - backup_ifnames() - for i = 1, #old_ifs do - net:del_interface(old_ifs[i]) - end - for i = 1, #new_ifs do - net:add_interface(new_ifs[i]) - end - break - end - end - end -end - - -if not net:is_virtual() then - ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface")) - ifname_multi.template = "cbi/network_ifacelist" - ifname_multi.nobridges = net:is_bridge() - ifname_multi.noaliases = true - ifname_multi.rmempty = false - ifname_multi.network = arg[1] - ifname_multi.widget = "checkbox" - ifname_multi:depends("type", "bridge") - ifname_multi.cfgvalue = ifname_single.cfgvalue - ifname_multi.write = ifname_single.write -end - - -if has_firewall then - fwzone = s:taboption("firewall", Value, "_fwzone", - translate("Create / Assign firewall-zone"), - translate("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 create field to define a new zone and attach the interface to it.")) - - fwzone.template = "cbi/firewall_zonelist" - fwzone.network = arg[1] - - function fwzone.cfgvalue(self, section) - self.iface = section - local z = fw:get_zone_by_network(section) - return z and z:name() - end - - function fwzone.write(self, section, value) - local zone = fw:get_zone(value) or fw:add_zone(value) - if zone then - fw:del_network(section) - zone:add_network(section) - end - end - - function fwzone.remove(self, section) - fw:del_network(section) - end -end - - -function p.write() end -function p.remove() end -function p.validate(self, value, section) - if value == net:proto() then - if not net:is_floating() and net:is_empty() then - local ifn = ((br and (br:formvalue(section) == "bridge")) - and ifname_multi:formvalue(section) - or ifname_single:formvalue(section)) - - for ifn in ut.imatch(ifn) do - return value - end - return nil, translate("The selected protocol needs a device assigned") - end - end - return value -end - - -local form, ferr = loadfile( - ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto() -) - -if not form then - s:taboption("general", DummyValue, "_error", - translate("Missing protocol extension for proto %q" % net:proto()) - ).value = ferr -else - setfenv(form, getfenv(1))(m, s, net) -end - - -local _, field -for _, field in ipairs(s.children) do - if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then - if next(field.deps) then - local _, dep - for _, dep in ipairs(field.deps) do - dep.proto = net:proto() - end - else - field:depends("proto", net:proto()) - end - end -end - - --- --- Display DNS settings if dnsmasq is available --- - -if has_dnsmasq and net:proto() == "static" then - m2 = Map("dhcp", "", "") - - local has_section = false - - m2.uci:foreach("dhcp", "dhcp", function(s) - if s.interface == arg[1] then - has_section = true - return false - end - end) - - if not has_section and has_dnsmasq then - - s = m2:section(TypedSection, "dhcp", translate("DHCP Server")) - s.anonymous = true - s.cfgsections = function() return { "_enable" } end - - x = s:option(Button, "_enable") - x.title = translate("No DHCP Server configured for this interface") - x.inputtitle = translate("Setup DHCP Server") - x.inputstyle = "apply" - - elseif has_section then - - s = m2:section(TypedSection, "dhcp", translate("DHCP Server")) - s.addremove = false - s.anonymous = true - s:tab("general", translate("General Setup")) - s:tab("advanced", translate("Advanced Settings")) - s:tab("ipv6", translate("IPv6 Settings")) - - function s.filter(self, section) - return m2.uci:get("dhcp", section, "interface") == arg[1] - end - - local ignore = s:taboption("general", Flag, "ignore", - translate("Ignore interface"), - translate("Disable DHCP for " .. - "this interface.")) - - local start = s:taboption("general", Value, "start", translate("Start"), - translate("Lowest leased address as offset from the network address.")) - start.optional = true - start.datatype = "or(uinteger,ip4addr)" - start.default = "100" - - local limit = s:taboption("general", Value, "limit", translate("Limit"), - translate("Maximum number of leased addresses.")) - limit.optional = true - limit.datatype = "uinteger" - limit.default = "150" - - local ltime = s:taboption("general", Value, "leasetime", translate("Lease time"), - translate("Expiry time of leased addresses, minimum is 2 minutes (2m).")) - ltime.rmempty = true - ltime.default = "12h" - - local dd = s:taboption("advanced", Flag, "dynamicdhcp", - translate("Dynamic DHCP"), - translate("Dynamically allocate DHCP addresses for clients. If disabled, only " .. - "clients having static leases will be served.")) - dd.default = dd.enabled - - s:taboption("advanced", Flag, "force", translate("Force"), - translate("Force DHCP on this network even if another server is detected.")) - - -- XXX: is this actually useful? - --s:taboption("advanced", Value, "name", translate("Name"), - -- translate("Define a name for this network.")) - - mask = s:taboption("advanced", Value, "netmask", - translate("IPv4-Netmask"), - translate("Override the netmask sent to clients. Normally it is calculated " .. - "from the subnet that is served.")) - - mask.optional = true - mask.datatype = "ip4addr" - - s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"), - translate("Define additional DHCP options, for example \"6,192.168.2.1," .. - "192.168.2.2\" which advertises different DNS servers to clients.")) - - for i, n in ipairs(s.children) do - if n ~= ignore then - n:depends("ignore", "") - end - end - - o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service")) - o:value("", translate("disabled")) - o:value("server", translate("server mode")) - o:value("relay", translate("relay mode")) - o:value("hybrid", translate("hybrid mode")) - - o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service")) - o:value("", translate("disabled")) - o:value("server", translate("server mode")) - o:value("relay", translate("relay mode")) - o:value("hybrid", translate("hybrid mode")) - - o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy")) - o:value("", translate("disabled")) - o:value("relay", translate("relay mode")) - o:value("hybrid", translate("hybrid mode")) - - o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"), - translate("Default is stateless + stateful")) - o:value("0", translate("stateless")) - o:value("1", translate("stateless + stateful")) - o:value("2", translate("stateful-only")) - o:depends("dhcpv6", "server") - o:depends("dhcpv6", "hybrid") - o.default = "1" - - o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"), - translate("Announce as default router even if no public prefix is available.")) - o:depends("ra", "server") - o:depends("ra", "hybrid") - - s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers")) - s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains")) - - else - m2 = nil - end -end - - -return m, m2 diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/network.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/network.lua deleted file mode 100644 index b98086dea..000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/network.lua +++ /dev/null @@ -1,202 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2008 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local tpl = require "luci.template" -local ntm = require "luci.model.network".init() -local fwm = require "luci.model.firewall".init() -local json = require "luci.jsonc" - -m = Map("network", translate("Interfaces")) -m:chain("wireless") -m:chain("firewall") -m:chain("dhcp") -m.pageaction = false - - -local _, net -local ifaces, netlist = { }, { } - -for _, net in ipairs(ntm:get_networks()) do - if net:name() ~= "loopback" then - local zn = net:zonename() - local z = zn and fwm:get_zone(zn) or fwm:get_zone_by_network(net:name()) - - local w = 1 - if net:is_alias() then - w = 2 - elseif net:is_dynamic() then - w = 3 - end - - ifaces[#ifaces+1] = net:name() - netlist[#netlist+1] = { - net:name(), z and z:name() or "-", z, net, w - } - end -end - -table.sort(netlist, - function(a, b) - if a[2] ~= b[2] then - return a[2] < b[2] - elseif a[5] ~= b[5] then - return a[5] < b[5] - else - return a[1] < b[1] - end - end) - -s = m:section(TypedSection, "interface", translate("Interface Overview")) -s.template = "admin_network/iface_overview" -s.netlist = netlist - -function s.cfgsections(self) - local _, net, sl = nil, nil, { } - - for _, net in ipairs(netlist) do - sl[#sl+1] = net[1] - end - - return sl -end - -o = s:option(Value, "__disable__") - -function o.write(self, sid, value) - if value ~= "1" then - m:set(sid, "auto", "") - else - m:set(sid, "auto", "0") - end -end - -o.remove = o.write - -o = s:option(Value, "__delete__") - -function o.write(self, sid, value) - ntm:del_network(sid) -end - - -if fs.access("/etc/init.d/dsl_control") then - local ok, boarddata = pcall(json.parse, fs.readfile("/etc/board.json")) - local modemtype = (ok == true) - and (type(boarddata) == "table") - and (type(boarddata.dsl) == "table") - and (type(boarddata.dsl.modem) == "table") - and boarddata.dsl.modem.type - - dsl = m:section(TypedSection, "dsl", translate("DSL")) - dsl.anonymous = true - - annex = dsl:option(ListValue, "annex", translate("Annex")) - annex:value("a", translate("Annex A + L + M (all)")) - annex:value("b", translate("Annex B (all)")) - annex:value("j", translate("Annex J (all)")) - annex:value("m", translate("Annex M (all)")) - annex:value("bdmt", translate("Annex B G.992.1")) - annex:value("b2", translate("Annex B G.992.3")) - annex:value("b2p", translate("Annex B G.992.5")) - annex:value("at1", translate("ANSI T1.413")) - annex:value("admt", translate("Annex A G.992.1")) - annex:value("alite", translate("Annex A G.992.2")) - annex:value("a2", translate("Annex A G.992.3")) - annex:value("a2p", translate("Annex A G.992.5")) - annex:value("l", translate("Annex L G.992.3 POTS 1")) - annex:value("m2", translate("Annex M G.992.3")) - annex:value("m2p", translate("Annex M G.992.5")) - - tone = dsl:option(ListValue, "tone", translate("Tone")) - tone:value("", translate("auto")) - tone:value("a", translate("A43C + J43 + A43")) - tone:value("av", translate("A43C + J43 + A43 + V43")) - tone:value("b", translate("B43 + B43C")) - tone:value("bv", translate("B43 + B43C + V43")) - - if modemtype == "vdsl" then - xfer_mode = dsl:option(ListValue, "xfer_mode", translate("Encapsulation mode")) - xfer_mode:value("", translate("auto")) - xfer_mode:value("atm", translate("ATM (Asynchronous Transfer Mode)")) - xfer_mode:value("ptm", translate("PTM/EFM (Packet Transfer Mode)")) - - line_mode = dsl:option(ListValue, "line_mode", translate("DSL line mode")) - line_mode:value("", translate("auto")) - line_mode:value("adsl", translate("ADSL")) - line_mode:value("vdsl", translate("VDSL")) - - ds_snr = dsl:option(ListValue, "ds_snr_offset", translate("Downstream SNR offset")) - ds_snr.default = "0" - for i = -100, 100, 5 do - ds_snr:value(i, translatef("%.1f dB", i / 10)) - end - end - - firmware = dsl:option(Value, "firmware", translate("Firmware File")) - - m.pageaction = true -end - --- Show ATM bridge section if we have the capabilities -if fs.access("/usr/sbin/br2684ctl") then - atm = m:section(TypedSection, "atm-bridge", translate("ATM Bridges"), - translate("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.")) - - atm.addremove = true - atm.anonymous = true - - atm.create = function(self, section) - local sid = TypedSection.create(self, section) - local max_unit = -1 - - m.uci:foreach("network", "atm-bridge", - function(s) - local u = tonumber(s.unit) - if u ~= nil and u > max_unit then - max_unit = u - end - end) - - m.uci:set("network", sid, "unit", max_unit + 1) - m.uci:set("network", sid, "atmdev", 0) - m.uci:set("network", sid, "encaps", "llc") - m.uci:set("network", sid, "payload", "bridged") - m.uci:set("network", sid, "vci", 35) - m.uci:set("network", sid, "vpi", 8) - - return sid - end - - atm:tab("general", translate("General Setup")) - atm:tab("advanced", translate("Advanced Settings")) - - vci = atm:taboption("general", Value, "vci", translate("ATM Virtual Channel Identifier (VCI)")) - vpi = atm:taboption("general", Value, "vpi", translate("ATM Virtual Path Identifier (VPI)")) - encaps = atm:taboption("general", ListValue, "encaps", translate("Encapsulation mode")) - encaps:value("llc", translate("LLC")) - encaps:value("vc", translate("VC-Mux")) - - atmdev = atm:taboption("advanced", Value, "atmdev", translate("ATM device number")) - unit = atm:taboption("advanced", Value, "unit", translate("Bridge unit number")) - payload = atm:taboption("advanced", ListValue, "payload", translate("Forwarding mode")) - payload:value("bridged", translate("bridged")) - payload:value("routed", translate("routed")) - m.pageaction = true -end - -local network = require "luci.model.network" -if network:has_ipv6() then - local s = m:section(NamedSection, "globals", "globals", translate("Global network options")) - local o = s:option(Value, "ula_prefix", translate("IPv6 ULA-Prefix")) - o.datatype = "ip6addr" - o.rmempty = true - m.pageaction = true -end - - -return m diff --git a/modules/luci-mod-network/luasrc/view/admin_network/iface_overview.htm b/modules/luci-mod-network/luasrc/view/admin_network/iface_overview.htm deleted file mode 100644 index 9d4afd2b2..000000000 --- a/modules/luci-mod-network/luasrc/view/admin_network/iface_overview.htm +++ /dev/null @@ -1,53 +0,0 @@ -
-
- <% - for i, net in ipairs(self.netlist) do - local z = net[3] - local c = z and z:get_color() or "#EEEEEE" - local t = z and translate("Part of zone %q") % z:name() or translate("No zone assigned") - local disabled = (net[4]:get("auto") == "0") - local dynamic = net[4]:is_dynamic() - %> -
-
-
-
- <%=net[1]:upper()%> -
-
-
- ? -
-
-
-
- <%:Collecting data...%> -
-
-
- /> - - <% if disabled then %> - - /> - <% else %> - - /> - <% end %> - - '" title="<%:Edit this interface%>" value="<%:Edit%>" id="<%=net[1]%>-ifc-edit"<%=ifattr(dynamic, "disabled", "disabled")%> /> - - - /> -
-
-
- <% end %> -
-
- -
- '" /> -
- - -- 2.25.1