luci-mod-network: switch to client side interface configuration pages

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2019-08-20 15:39:16 +02:00
parent 6a2a53a829
commit e4bc192012
6 changed files with 1042 additions and 934 deletions

View file

@ -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 <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
igmp = s.taboption('physical', form.Flag, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> 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') + ' &#160; '),
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 <abbr title=\'Dynamic Host Configuration Protocol\'>DHCP</abbr> 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 (<code>2m</code>).'));
so.optional = true;
so.default = '12h';
so = ss.taboption('advanced', form.Flag, 'dynamicdhcp', _('Dynamic <abbr title=\'Dynamic Host Configuration Protocol\'>DHCP</abbr>'), _('Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.'));
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', _('<abbr title=\'Internet Protocol Version 4\'>IPv4</abbr>-Netmask'), _('Override the netmask sent to clients. Normally it is calculated from the subnet that is served.'));
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 \'<code>6,192.168.2.1,192.168.2.2</code>\' 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));
}
});

View file

@ -76,30 +76,19 @@ function index()
end 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 = entry({"admin", "network", "iface_status"}, call("iface_status"), nil)
page.leaf = true page.leaf = true
page = entry({"admin", "network", "iface_reconnect"}, post("iface_reconnect"), nil) page = entry({"admin", "network", "iface_reconnect"}, post("iface_reconnect"), nil)
page.leaf = true 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.leaf = true
page.subindex = 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 if nixio.fs.access("/etc/config/dhcp") then
page = node("admin", "network", "dhcp") page = node("admin", "network", "dhcp")
@ -268,6 +257,62 @@ function iface_reconnect(iface)
luci.http.status(404, "No such interface") luci.http.status(404, "No such interface")
end 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) function wifi_status(devs)
local s = require "luci.tools.status" local s = require "luci.tools.status"
local rv = { } local rv = { }

View file

@ -1,101 +0,0 @@
-- Copyright 2009-2010 Jo-Philipp Wich <jow@openwrt.org>
-- 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: <code>A-Z</code>, <code>a-z</code>, " ..
"<code>0-9</code> and <code>_</code>"
))
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

View file

@ -1,563 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
-- 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 <abbr title=\"Virtual Local Area Network\">VLAN</abbr> notation <samp>INTERFACE.VLANNR</samp> (<abbr title=\"for example\">e.g.</abbr>: <samp>eth0.1</samp>)."))
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 <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
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 <abbr title=\"Internet Group Management Protocol\">IGMP</abbr> 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 <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> 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 <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> 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 (<code>2m</code>)."))
ltime.rmempty = true
ltime.default = "12h"
local dd = s:taboption("advanced", Flag, "dynamicdhcp",
translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
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("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-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 \"<code>6,192.168.2.1," ..
"192.168.2.2</code>\" 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

View file

@ -1,202 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
-- 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

View file

@ -1,53 +0,0 @@
<div class="cbi-section-node">
<div class="table">
<%
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()
%>
<div class="tr cbi-rowstyle-<%=i % 2 + 1%>">
<div class="td col-3 center middle">
<div class="ifacebox">
<div class="ifacebox-head" style="background-color:<%=c%>" title="<%=pcdata(t)%>">
<strong><%=net[1]:upper()%></strong>
</div>
<div class="ifacebox-body" id="<%=net[1]%>-ifc-devices" data-network="<%=net[1]%>">
<img src="<%=resource%>/icons/ethernet_disabled.png" style="width:16px; height:16px" /><br />
<small>?</small>
</div>
</div>
</div>
<div class="td col-5 left middle" id="<%=net[1]%>-ifc-description">
<em><%:Collecting data...%></em>
</div>
<div class="td cbi-section-actions">
<div>
<input type="button" class="cbi-button cbi-button-neutral" onclick="iface_reconnect('<%=net[1]%>')" title="<%:Reconnect this interface%>" value="<%:Restart%>"<%=ifattr(disabled or dynamic, "disabled", "disabled")%> />
<% if disabled then %>
<input type="hidden" name="cbid.network.<%=net[1]%>.__disable__" value="1" />
<input type="submit" name="cbi.apply" class="cbi-button cbi-button-neutral" onclick="this.previousElementSibling.value='0'" title="<%:Reconnect this interface%>" value="<%:Connect%>"<%=ifattr(dynamic, "disabled", "disabled")%> />
<% else %>
<input type="hidden" name="cbid.network.<%=net[1]%>.__disable__" value="0" />
<input type="submit" name="cbi.apply" class="cbi-button cbi-button-neutral" onclick="this.previousElementSibling.value='1'" title="<%:Shutdown this interface%>" value="<%:Stop%>"<%=ifattr(dynamic, "disabled", "disabled")%> />
<% end %>
<input type="button" class="cbi-button cbi-button-action important" onclick="location.href='<%=url("admin/network/network", net[1])%>'" title="<%:Edit this interface%>" value="<%:Edit%>" id="<%=net[1]%>-ifc-edit"<%=ifattr(dynamic, "disabled", "disabled")%> />
<input type="hidden" name="cbid.network.<%=net[1]%>.__delete__" value="" />
<input type="submit" name="cbi.apply" class="cbi-button cbi-button-negative" onclick="iface_delete(event)" value="<%:Delete%>"<%=ifattr(dynamic, "disabled", "disabled")%> />
</div>
</div>
</div>
<% end %>
</div>
</div>
<div class="cbi-section-create">
<input type="button" class="cbi-button cbi-button-add" value="<%:Add new interface...%>" onclick="location.href='<%=url("admin/network/iface_add")%>'" />
</div>
<script type="text/javascript" src="<%=resource%>/view/network/network.js"></script>