'use strict';'require view';'require dom';'require poll';'require fs';'require ui';'require uci';'require rpc';'require form';'require network';'require firewall';'require tools.widgets as widgets';'require tools.network as nettools';const callNetworkDeviceStatus=rpc.declare({object:'network.device',method:'status',params:['name'],expect:{'':{}}});var isReadonlyView=!L.hasViewPermission()||null;function count_changes(section_id){var changes=ui.changes.changes,n=0;if(!L.isObject(changes))
return n;if(Array.isArray(changes.network))
for(let c of changes.network)
n+=(c[1]==section_id);if(Array.isArray(changes.dhcp))
for(let c of changes.dhcp)
n+=(c[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.svg').format(alias?'alias':type,up?'':'_disabled')}),E('span',{'class':'cbi-tooltip ifacebadge large'},[E('img',{'src':L.resource('icons/%s%s.svg').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){let desc=null;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'))]);desc=desc?'%s (%s)'.format(desc,ifc.getI18n()):ifc.getI18n();const changecount=with_device?0:count_changes(ifc.getName());const maindev=ifc.getL3Device()??ifc.getDevice();const macaddr=maindev?maindev.getMAC():null;const carrier=maindev?maindev.getCarrier():null;const cond00=!changecount&&!ifc.isDynamic()&&!ifc.isAlias();const cond01=cond00&&macaddr;const cond02=cond00&&maindev;const cond03=cond00&&carrier;function addEntries(label,array){return Array.isArray(array)?array.flatMap((item)=>[label,item]):[label,null];}
return L.itemlist(node,[_('Protocol'),with_device?null:(desc||'?'),_('Device'),with_device?(maindev?maindev.getShortName():E('em',_('Not present'))):null,_('Carrier'),(cond03)?_('Present'):_('Absent'),_('Uptime'),(!changecount&&ifc.isUp())?'%t'.format(ifc.getUptime()):null,_('MAC'),(cond01)?macaddr:null,_('RX'),(cond02)?'%.2mB (%d %s)'.format(maindev.getRXBytes(),maindev.getRXPackets(),_('Pkts.')):null,_('TX'),(cond02)?'%.2mB (%d %s)'.format(maindev.getTXBytes(),maindev.getTXPackets(),_('Pkts.')):null,...addEntries(_('IPv4'),changecount?[]:ifc.getIPAddrs()),...addEntries(_('IPv6'),changecount?[]:ifc.getIP6Addrs()),...addEntries(_('IPv6-PD'),changecount?null:ifc.getIP6Prefixes?.()),_('Information'),with_device?null:(ifc.get('disabled')!='1'?null:_('Interface disabled')),_('Information'),with_device?null:(ifc.get('auto')!='0'?null:_('Not started on boot')),...addEntries(_('Error'),ifc.getErrors()),null,changecount?E('a',{href:'#',click:L.bind(ui.changes.displayChanges,ui.changes)},_('Interface has %d pending changes').format(changecount)):null]);}
function render_modal_status(node,ifc){const dev=ifc?(ifc.getL3Device()??ifc.getDevice()):null;dom.content(node,[E('img',{'src':L.resource('icons/%s%s.svg').format(dev?dev.getType():'ethernet',ifc.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){const dev=ifc.getL3Device()||ifc.getDevice();const subdevs=dev?dev.getPorts():null;const c=[render_iface(dev,ifc.isAlias())];if(subdevs&&subdevs.length){var sifs=[' ('];for(var j=0;j<subdevs.length;j++)
sifs.push(render_iface(subdevs[j]));sifs.push(')');c.push(E('span',{},sifs));}
c.push(E('br'));c.push(E('small',{},ifc.isAlias()?_('Alias of "%s"').format(ifc.isAlias()):(dev?dev.getName():E('em',_('Not present')))));dom.content(node,c);return firewall.getZoneByNetwork(ifc.getName()).then(L.bind(function(zone){this.style.backgroundColor=zone?zone.getColor():'#EEEEEE';this.title=zone?_('Part of zone %q').format(zone.getName()):_('No zone assigned');},node.previousElementSibling));}
function iface_updown(up,id,ev,force){const row=document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id));const dsc=row.querySelector('[data-name="_ifacestat"] > div');const btns=row.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down');btns[+!up].blur();btns[+!up].classList.add('spinning');btns[0].disabled=true;btns[1].disabled=true;if(!up){L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res){var info=null;try{info=JSON.parse(res);}catch(e){}
if(L.isObject(info)&&Array.isArray(info.inbound_interfaces)&&info.inbound_interfaces.filter(function(i){return i==id})[0]){ui.showModal(_('Confirm disconnect'),[E('p',_('You appear to be currently connected to the device via the "%h" interface. Do you really want to shut down the interface?').format(id)),E('div',{'class':'button-row'},[E('button',{'class':'btn cbi-button cbi-button-neutral','click':function(ev){btns[1].classList.remove('spinning');btns[1].disabled=false;btns[0].disabled=false;ui.hideModal();}},_('Cancel')),' ',E('button',{'class':'btn cbi-button cbi-button-negative important','click':function(ev){dsc.setAttribute('disconnect','');dom.content(dsc,E('em',_('Interface is shutting down...')));ui.hideModal();}},_('Disconnect'))])]);}
else{dsc.setAttribute('disconnect','');dom.content(dsc,E('em',_('Interface is shutting down...')));}});}
else{dsc.setAttribute(up?'reconnect':'disconnect',force?'force':'');dom.content(dsc,E('em',up?_('Interface is reconnecting...'):_('Interface is shutting down...')));}}
function get_netmask(s,use_cfgvalue){const readfn=use_cfgvalue?'cfgvalue':'formvalue';const addrs=L.toArray(s[readfn](s.section,'ipaddr'));const mask=s[readfn](s.section,'netmask');const firstsubnet=mask?addrs[0]+'/'+mask:addrs.filter(function(a){return a.indexOf('/')>0})[0];if(firstsubnet==null)
return null;let subnetmask=firstsubnet.split('/')[1];if(!isNaN(subnetmask))
subnetmask=network.prefixToMask(+subnetmask);return subnetmask;}
function has_peerdns(proto){switch(proto){case'dhcp':case'dhcpv6':case'qmi':case'ppp':case'pppoe':case'pppoa':case'pptp':case'openvpn':case'sstp':return true;}
return false;}
function has_sourcefilter(proto){switch(proto){case'3g':case'dhcpv6':case'directip':case'mbim':case'modemmanager':case'ncm':case'ppp':case'pppoa':case'pppoe':case'pptp':case'qmi':return true;}
return false;}
return view.extend({poll_status(map,networks){let resolveZone=null;for(let ifc of networks){const row=map.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc.getName()));if(row==null)
continue;const dsc=row.querySelector('[data-name="_ifacestat"] > div');const box=row.querySelector('[data-name="_ifacebox"] .ifacebox-body');const btn1=row.querySelector('.cbi-section-actions .reconnect');const btn2=row.querySelector('.cbi-section-actions .down');const stat=document.querySelector('[id="%s-ifc-status"]'.format(ifc.getName()));resolveZone=render_ifacebox_status(box,ifc);const disabled=ifc?!ifc.isUp():true;const dynamic=ifc?ifc.isDynamic():false;const pending=ifc?ifc.isPending():false;if(dsc.hasAttribute('reconnect')){dom.content(dsc,E('em',_('Interface is starting...')));}
else if(dsc.hasAttribute('disconnect')){dom.content(dsc,E('em',_('Interface is stopping...')));}
else if(ifc.getProtocol()||uci.get('network',ifc.getName())==null){render_status(dsc,ifc,false);}
else if(!ifc.getProtocol()){const e=map.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc.getName()));if(e)e.disabled=true;const link=L.url('admin/system/package-manager')+'?query=luci-proto';dom.content(dsc,[E('em',_('Unsupported protocol type.')),E('br'),E('a',{href:link},_('Install protocol extensions...'))]);}
else{dom.content(dsc,E('em',_('Interface not present or not connected yet.')));}
if(stat){const dev=ifc.getL3Device()??ifc.getDevice();dom.content(stat,[E('img',{'src':L.resource('icons/%s%s.svg').format(dev?dev.getType():'ethernet',ifc.isUp()?'':'_disabled'),'title':dev?dev.getTypeI18n():_('Not present')}),render_status(E('span'),ifc,true)]);}
if(isReadonlyView===true){btn1.disabled=true;btn2.disabled=true;}
else if(btn1.classList.contains('spinning')||btn2.classList.contains('spinning')){btn1.disabled=true;btn2.disabled=true;}
else if(dynamic===true){btn1.disabled=true;btn2.disabled=true;}
else if(pending===true){btn1.disabled=true;btn2.disabled=false;}
else if(disabled===true){btn1.disabled=false;btn2.disabled=true;}
else{btn1.disabled=false;btn2.disabled=false;}}
document.querySelectorAll('.port-status-device[data-device]').forEach(function(node){nettools.updateDevBadge(node,network.instantiateDevice(node.getAttribute('data-device')));});document.querySelectorAll('.port-status-link[data-device]').forEach(function(node){nettools.updatePortStatus(node,network.instantiateDevice(node.getAttribute('data-device')));});return Promise.all([resolveZone,network.flushCache()]);},load(){return Promise.all([network.getDSLModemType(),network.getDevices(),fs.lines('/etc/iproute2/rt_tables'),uci.changes()]);},interfaceBridgeWithIfnameSections(){return uci.sections('network','interface').filter(function(ns){return ns.type=='bridge'&&!ns.ports&&ns.ifname;});},deviceWithIfnameSections(){return uci.sections('network','device').filter(function(ns){return ns.type=='bridge'&&!ns.ports&&ns.ifname;});},interfaceWithIfnameSections(){return uci.sections('network','interface').filter(function(ns){return!ns.device&&ns.ifname;});},handleBridgeMigration(ev){const tasks=[];this.interfaceBridgeWithIfnameSections().forEach(function(ns){const device_name='br-'+ns['.name'];tasks.push(uci.callAdd('network','device',null,{'name':device_name,'type':'bridge','ports':L.toArray(ns.ifname),'mtu':ns.mtu,'macaddr':ns.macaddr,'igmp_snooping':ns.igmp_snooping}));tasks.push(uci.callSet('network',ns['.name'],{'type':'','ifname':'','mtu':'','macaddr':'','igmp_snooping':'','device':device_name}));});return Promise.all(tasks).then(L.bind(ui.changes.init,ui.changes)).then(L.bind(ui.changes.apply,ui.changes));},renderBridgeMigration(){ui.showModal(_('Network bridge configuration migration'),[E('p',_('The existing network configuration needs to be changed for LuCI to function properly.')),E('p',_('Upon pressing "Continue", bridges configuration will be updated and the network will be restarted to apply the updated configuration.')),E('div',{'class':'right'},E('button',{'class':'btn cbi-button-action important','click':ui.createHandlerFn(this,'handleBridgeMigration')},_('Continue')))]);},handleIfnameMigration(ev){const tasks=[];this.deviceWithIfnameSections().forEach(function(ds){tasks.push(uci.callSet('network',ds['.name'],{'ifname':'','ports':L.toArray(ds.ifname)}));});this.interfaceWithIfnameSections().forEach(function(ns){tasks.push(uci.callSet('network',ns['.name'],{'ifname':'','device':ns.ifname}));});return Promise.all(tasks).then(L.bind(ui.changes.init,ui.changes)).then(L.bind(ui.changes.apply,ui.changes));},renderIfnameMigration(){ui.showModal(_('Network ifname configuration migration'),[E('p',_('The existing network configuration needs to be changed for LuCI to function properly.')),E('p',_('Upon pressing "Continue", ifname options will get renamed and the network will be restarted to apply the updated configuration.')),E('div',{'class':'right'},E('button',{'class':'btn cbi-button-action important','click':ui.createHandlerFn(this,'handleIfnameMigration')},_('Continue')))]);},render([dslModemType,netDevs,rtTables]){if(this.interfaceBridgeWithIfnameSections().length)
return this.renderBridgeMigration();else if(this.deviceWithIfnameSections().length||this.interfaceWithIfnameSections().length)
return this.renderIfnameMigration();let m,s,o;rtTables=rtTables.map(function(l){const m=l.trim().match(/^(\d+)\s+(\S+)$/);return m?[+m[1],m[2]]:null;}).filter(function(e){return e&&e[0]>0;});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.filterrow=true;s.addbtntitle=_('Add new interface...');s.load=function(){return Promise.all([network.getNetworks(),firewall.getZones(),uci.load('system')]).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('brport',_('Bridge port specific options'));s.tab('bridgevlan',_('Bridge VLAN filtering'));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;};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,pending=net?net.isPending():false;dom.content(tdEl.lastChild,[E('button',{'class':'cbi-button cbi-button-neutral reconnect','click':iface_updown.bind(this,true,section_id),'title':_('Reconnect this interface'),},_('Restart')),E('button',{'class':'cbi-button cbi-button-neutral down','click':iface_updown.bind(this,false,section_id),'title':_('Shutdown this interface'),},_('Stop')),tdEl.lastChild.firstChild,tdEl.lastChild.lastChild]);if(!dynamic&&net&&!uci.get('network',net.getName())){tdEl.lastChild.childNodes[0].disabled=true;tdEl.lastChild.childNodes[1].disabled=true;tdEl.lastChild.childNodes[2].disabled=true;tdEl.lastChild.childNodes[3].disabled=true;}
else if(dynamic===true){tdEl.lastChild.childNodes[0].disabled=true;tdEl.lastChild.childNodes[1].disabled=true;tdEl.lastChild.childNodes[2].disabled=true;tdEl.lastChild.childNodes[3].disabled=true;}
else if(pending===true){tdEl.lastChild.childNodes[0].disabled=true;tdEl.lastChild.childNodes[1].disabled=false;tdEl.lastChild.childNodes[2].disabled=false;tdEl.lastChild.childNodes[3].disabled=false;}
else if(disabled===true){tdEl.lastChild.childNodes[0].disabled=false;tdEl.lastChild.childNodes[1].disabled=true;tdEl.lastChild.childNodes[2].disabled=false;tdEl.lastChild.childNodes[3].disabled=false;}
else{tdEl.lastChild.childNodes[0].disabled=false;tdEl.lastChild.childNodes[1].disabled=false;tdEl.lastChild.childNodes[2].disabled=false;tdEl.lastChild.childNodes[3].disabled=false;}
return tdEl;};s.addModalOptions=function(s){const protoval=uci.get('network',s.section,'proto')||'none';let o,proto_select,proto_switch,ss,so;return network.getNetwork(s.section).then(L.bind(function(ifc){const protocols=network.getProtocols();protocols.sort(function(a,b){return L.naturalCompare(a.getProtocol(),b.getProtocol());});o=s.taboption('general',form.DummyValue,'_ifacestat_modal',_('Status'));o.modalonly=true;o.cfgvalue=L.bind(function(section_id){const 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_select.default='none';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',widgets.DeviceSelect,'_net_device',_('Device'));o.ucioption='device';o.nobridges=false;o.optional=false;o.network=ifc.getName();o.exclude='@'+ifc.getName();o=s.taboption('general',form.Flag,'disabled',_('Disable this interface'));o.modalonly=true;o=s.taboption('general',form.Flag,'auto',_('Bring up on boot'));o.modalonly=true;o.default=o.enabled;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 <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>custom</em> field to define a new zone and attach the interface to it.'));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([old_zone,new_zone]){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(let p of protocols){proto_select.value(p.getProtocol(),p.getI18n());if(p.getProtocol()!=protoval)
proto_switch.depends('proto',p.getProtocol());}
if(L.hasSystemFeature('dnsmasq')||L.hasSystemFeature('odhcpd')){o=s.taboption('dhcp',form.SectionValue,'_dhcp',form.TypedSection,'dhcp');ss=o.subsection;ss.uciconfig='dhcp';ss.addremove=false;ss.anonymous=true;ss.tab('general',_('General Setup'));ss.tab('ipv4',_('IPv4 Settings'));ss.tab('ipv6',_('IPv6 Settings'));ss.tab('dhcpv6',_('DHCPv6 Settings'));ss.tab('ipv6-ra',_('IPv6 RA 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':_('Set up DHCP Server'),'click':ui.createHandlerFn(this,function(section_id,ev){this.map.save(function(){uci.add('dhcp','dhcp',section_id);uci.set('dhcp',section_id,'interface',section_id);if(protoval=='static'){uci.set('dhcp',section_id,'start',100);uci.set('dhcp',section_id,'limit',150);uci.set('dhcp',section_id,'leasetime','12h');uci.set('dhcp',section_id,'dhcpv4','server');}else{uci.set('dhcp',section_id,'ignore',1);}});},ifc.getName())},_('Set up DHCP Server'))]);};if(L.hasSystemFeature('dnsmasq')){ss.taboption('general',form.Flag,'ignore',_('Ignore interface'),_('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface (dnsmasq only).'));}
if(protoval=='static'){so=ss.taboption('general',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;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.validate=function(section_id,value){if(value==="infinite"||value==="deprecated"){return true;}
const regex=new RegExp("^[0-9]+[smhdw]?$","i");if(regex.test(value)){return true;}
return _("Invalid DHCP lease time format. Use integer values optionally followed by s, m, h, d, or w.");}
if(L.hasSystemFeature('dnsmasq')){ss.taboption('general',form.Flag,'force',_('Force'),_('Force DHCP on this network even if another server is detected (dnsmasq only).'));}
if(L.hasSystemFeature('dnsmasq')){ss.taboption('general',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 (dnsmasq only).'));ss.taboption('general',form.DynamicList,'dhcp_option_force',_('Force DHCP-Options'),_('As DHCP-Options; send unsolicited (dnsmasq only).'));}
if(L.hasSystemFeature('odhcpd','dhcpv4')||L.hasSystemFeature('dnsmasq')){so=ss.taboption('ipv4',form.RichListValue,'dhcpv4',_('DHCPv4 Service'),_('Enable or disable DHCPv4 services on this interface.'));so.optional=true;so.value('disabled',_('disabled'),_('Do not provide DHCPv4 services on this interface.'));so.value('server',_('enabled'),_('Provide DHCPv4 services on this interface.'));}
if(L.hasSystemFeature('odhcpd','dhcpv4')){so=ss.taboption('ipv4',form.Value,'ipv6_only_preferred',_('IPv6-Only Preferred'),_('Specifies how often (in seconds) clients should check whether IPv6-only mode is still preferred (see %s, odhcpd only).').format('<a href="%s" target="_blank">RFC8925</a>').format('https://www.rfc-editor.org/rfc/rfc8925'));so.optional=true;so.datatype='or(0, range(300,86400))';so.default='0';}
so=ss.taboption('ipv4',form.Value,'start',_('Start','DHCP IP range start address'),_('Lowest leased address as offset from the network address.'));so.optional=true;so.datatype='or(uinteger,ip4addr("nomask"))';so.default='100';so=ss.taboption('ipv4',form.Value,'limit',_('Limit'),_('Maximum number of leased addresses.'));so.optional=true;so.datatype='uinteger';so.default='150';if(L.hasSystemFeature('dnsmasq')){so=ss.taboption('ipv4',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 (dnsmasq only).'));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){const uielem=this.getUIElement(section_id);if(uielem)
uielem.setPlaceholder(get_netmask(s,false));return form.Value.prototype.validate.apply(this,[section_id,value]);};}}
const has_other_master=uci.sections('dhcp','dhcp').filter(function(s){return(s.interface!=ifc.getName()&&s.master=='1');})[0];so=ss.taboption('ipv6',form.Flag,'master',_('Designated master'));so.readonly=has_other_master?true:false;so.description=has_other_master?_('Interface "%h" is already marked as designated master.').format(has_other_master.interface||has_other_master['.name']):_('Set this interface as master for RA and DHCPv6 relaying as well as NDP proxying.');so.validate=function(section_id,value){const hybrid_downstream_desc=_('Operate in <em>relay mode</em> if a designated master interface is configured and active, otherwise fall back to <em>server mode</em>.');const ndp_downstream_desc=_('Operate in <em>relay mode</em> if a designated master interface is configured and active, otherwise disable <abbr title="Neighbour Discovery Protocol">NDP</abbr> proxying.');const hybrid_master_desc=_('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.');let ra_server_allowed=true;const checked=this.formvalue(section_id);const dhcpv6=this.section.getOption('dhcpv6').getUIElement(section_id);const ndp=this.section.getOption('ndp').getUIElement(section_id);const ra=this.section.getOption('ra').getUIElement(section_id);switch(protoval){case'dhcp':case'dhcpv6':case'3g':case'l2tp':case'ppp':case'pppoa':case'pppoe':case'pptp':case'pppossh':case'ipip':case'gre':case'grev6':ra_server_allowed=false;break;}
if(checked=='1'||!ra_server_allowed){dhcpv6.node.querySelector('li[data-value="server"]').setAttribute('unselectable','');if(dhcpv6.getValue()=='server')
dhcpv6.setValue('hybrid');ra.node.querySelector('li[data-value="server"]').setAttribute('unselectable','');if(ra.getValue()=='server')
ra.setValue('hybrid');}
if(checked=='1'){dhcpv6.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML=hybrid_master_desc;ra.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML=hybrid_master_desc;ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML=hybrid_master_desc;}
else{if(ra_server_allowed){dhcpv6.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');ra.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');}
dhcpv6.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML=hybrid_downstream_desc;ra.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML=hybrid_downstream_desc;ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML=ndp_downstream_desc;}
return true;};so=ss.taboption('ipv6',form.RichListValue,'ra',_('<abbr title="Router Advertisement">RA</abbr>-Service'),_('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));so.optional=true;so.value('',_('disabled'),_('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));so.value('server',_('server mode'),_('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));so.value('relay',_('relay mode'),_('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));so.value('hybrid',_('hybrid mode'),' ');so=ss.taboption('ipv6-ra',form.RichListValue,'ra_default',_('Default router'),_('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));so.optional=true;so.value('',_('automatic'),_('Announce this device as default router if a local IPv6 default route is present.'));so.value('1',_('on available prefix'),_('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));so.value('2',_('forced'),_('Announce this device as default router regardless of whether a prefix or default route is present.'));so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Flag,'ra_slaac',_('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),_('Set the autonomous address-configuration flag in the prefix information options of sent <abbr title="Router Advertisement">RA</abbr> messages. When enabled, clients will perform stateless IPv6 address autoconfiguration.'));so.default=so.enabled;so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.ListValue,'ra_preference',_('Router Priority'),_('A tie-breaker for clients and their routes when multiple routers exist on the same network.'));so.default='medium';so.value('low',_('Low'));so.value('medium',_('Medium'));so.value('high',_('High'));so.depends('ra','server');so=ss.taboption('ipv6-ra',form.RichListValue,'ra_flags',_('<abbr title="Router Advertisement">RA</abbr> Flags'),_('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));so.optional=true;so.value('managed-config',_('managed config (M)'),_('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));so.value('other-config',_('other config (O)'),_('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));so.value('home-agent',_('mobile home agent (H)'),_('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));so.multiple=true;so.select_placeholder=_('none');so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so.cfgvalue=function(section_id){const flags=L.toArray(uci.get('dhcp',section_id,'ra_flags'));return flags.length?flags:['other-config'];};so.remove=function(section_id){const existing=L.toArray(uci.get('dhcp',section_id,'ra_flags'));if(this.isActive(section_id)){if(existing.length!=1||existing[0]!='none')
uci.set('dhcp',section_id,'ra_flags',['none']);}
else if(existing.length){uci.unset('dhcp',section_id,'ra_flags');}};so=ss.taboption('ipv6-ra',form.RichListValue,'_ra_pio_flags',_('RA <abbr title="Prefix Information Option">PIO</abbr> Flags'),_('Allow <abbr title="Prefix Information Option">PIO</abbr> flags sent in RA messages, globally for all prefixes.'));so.optional=true;so.value('pd',_('prefix delegation (PD) preferred (P)'),_('The PD <em>preferred</em> (P) flag indicates that the network prefers clients use PD instead of individual addresses via DHCPv6 or SLAAC. Requires DHCPv6 and DHCPv6-PD.'));so.multiple=true;so.default='';so.select_placeholder=_('none');so.depends({ra:'server'});so.depends({ra:'hybrid',master:'0'});so.cfgvalue=function(section_id){const p_flag=uci.get('dhcp',section_id,'dhcpv6_pd_preferred');const flags=[];if(p_flag)flags.push('pd');return flags;};so.remove=function(section_id){uci.unset('dhcp',section_id,'dhcpv6_pd_preferred');};so.write=function(section_id,value){if(value?.includes('pd'))
uci.set('dhcp',section_id,'dhcpv6_pd_preferred','1');return;}
so=ss.taboption('ipv6-ra',form.Value,'ra_pref64',_('NAT64 prefix'),_('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.')+' '+
_('See %s and %s.').format('<a href="%s" target="_blank">RFC6146</a>','<a href="%s" target="_blank">RFC8781</a>').format('https://www.rfc-editor.org/rfc/rfc6146','https://www.rfc-editor.org/rfc/rfc8781'));so.optional=true;so.datatype='cidr6';so.placeholder='64:ff9b::/96';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'ra_maxinterval',_('Max <abbr title="Router Advertisement">RA</abbr> interval'),_('Maximum time allowed  between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 600 seconds.'));so.optional=true;so.datatype='uinteger';so.placeholder='600';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'ra_mininterval',_('Min <abbr title="Router Advertisement">RA</abbr> interval'),_('Minimum time allowed  between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 200 seconds.'));so.optional=true;so.datatype='uinteger';so.placeholder='200';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'ra_reachabletime',_('<abbr title="Router Advertisement">RA</abbr> Reachability Timer'),_('Units: milliseconds. 0 means unspecified.')+' '+
_('Dictates how long a node assumes a neighbor is reachable after a reachability confirmation; published in <abbr title="Router Advertisement">RA</abbr> messages.'));so.optional=true;so.datatype='range(0, 3600000)';so.placeholder='0';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'ra_retranstime',_('<abbr title="Router Advertisement">RA</abbr> Retransmission Timer'),_('Units: milliseconds. 0 means unspecified.')+' '+
_('Controls retransmitted Neighbor Solicitation messages; published in <abbr title="Router Advertisement">RA</abbr> messages.'));so.optional=true;so.placeholder='0';so.datatype='range(0, 60000)';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'ra_lifetime',_('<abbr title="Router Advertisement">RA</abbr> Lifetime'),_('Router Lifetime published  in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages.  Maximum is 9000 seconds.'));so.optional=true;so.datatype='range(0, 9000)';so.placeholder='2700';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'ra_mtu',_('<abbr title="Router Advertisement">RA</abbr> MTU'),_('The <abbr title="Maximum Transmission Unit">MTU</abbr>  to be published in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Minimum is 1280 bytes.'));so.optional=true;so.datatype='range(1280, 65535)';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so.load=function(section_id){const dev=ifc.getL3Device();const path=dev?"/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName()):null;return Promise.all([dev?L.resolveDefault(fs.read(path),dev.getMTU()):null,this.super('load',[section_id])]).then(L.bind(function(res){this.placeholder=+res[0];return res[1];},this));};so=ss.taboption('ipv6-ra',form.Value,'ra_hoplimit',_('<abbr title="Router Advertisement">RA</abbr> Hop Limit'),_('The maximum hops  to be published in <abbr title="Router Advertisement">RA</abbr> messages. Maximum is 255 hops.'));so.optional=true;so.datatype='range(0, 255)';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so.load=function(section_id){const dev=ifc.getL3Device();const path=dev?"/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName()):null;return Promise.all([dev?L.resolveDefault(fs.read(path),64):null,this.super('load',[section_id])]).then(L.bind(function(res){this.placeholder=+res[0];return res[1];},this));};so=ss.taboption('ipv6-ra',form.Value,'max_preferred_lifetime',_('IPv6 Preferred Prefix Lifetime'),_('Maximum preferred lifetime for a prefix.'));so.optional=true;so.placeholder='45m';so.value('5m',_('5m (5 minutes)'));so.value('45m',_('45m (45 minutes - default)'));so.value('3h',_('3h (3 hours)'));so.value('12h',_('12h (12 hours)'));so.value('7d',_('7d (7 days)'));so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6-ra',form.Value,'max_valid_lifetime',_('IPv6 Valid Prefix Lifetime'),_('Maximum valid lifetime for a prefix.'));so.optional=true;so.placeholder='90m';so.value('5m',_('5m (5 minutes)'));so.value('90m',_('90m (90 minutes - default)'));so.value('3h',_('3h (3 hours)'));so.value('12h',_('12h (12 hours)'));so.value('7d',_('7d (7 days)'));so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so=ss.taboption('ipv6',form.RichListValue,'dhcpv6',_('DHCPv6-Service'),_('Configures the operation mode of the DHCPv6 service on this interface.'));so.optional=true;so.value('',_('disabled'),_('Do not offer DHCPv6 service on this interface.'));so.value('server',_('server mode'),_('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));so.value('relay',_('relay mode'),_('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));so.value('hybrid',_('hybrid mode'),' ');so=ss.taboption('dhcpv6',form.Flag,'dhcpv6_pd',_('DHCPv6-<abbr title="Prefix Delegation">PD</abbr>'),_('Toggle IPv6 PD via DHCPv6.'));so.depends({dhcpv6:'server'});so=ss.taboption('dhcpv6',form.Value,'dhcpv6_pd_min_len',_('<abbr title="Prefix Delegation">PD</abbr> minimum length'),_('Configures the minimum delegated prefix length assigned to a requesting downstream router, potentially overriding a requested prefix length. If left unspecified, the device will assign the smallest available prefix greater than or equal to the requested prefix.'));so.placeholder='62';so.datatype='range(1,64)';so.depends({dhcpv6:'server',dhcpv6_pd:'1'});so=ss.taboption('ipv6',form.DynamicList,'dns',_('Announce IPv4/6 DNS servers'),_('Specifies a fixed list of DNS server addresses to announce via DHCPv6.')+'<br/>'+
_('Specifies a fixed list of DNS server addresses to announce via RA.')+'<br/>'+
_('If left unspecified, the device will announce itself as DNS server unless the <em>Local IPv6 DNS server</em> option is disabled.'));so.datatype='ipaddr("nomask")';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so.depends('dhcpv6','server');so.depends({dhcpv6:'hybrid',master:'0'});so=ss.taboption('ipv6',form.Flag,'dns_service',_('Local IPv6 DNS server'),_('Announce this device as IPv6 DNS server.'));so.default=so.enabled;so.depends({ra:'server',dns:/^$/});so.depends({ra:'hybrid',dns:/^$/,master:'0'});so.depends({dhcpv6:'server',dns:/^$/});so.depends({dhcpv6:'hybrid',dns:/^$/,master:'0'});so=ss.taboption('ipv6',form.DynamicList,'dnr',_('Announce encrypted DNS servers'),_('Specifies a fixed list of encrypted DNS server addresses to announce via DHCPv6/<abbr title="Router Advertisement">RA</abbr> (see %s).').format('<a href="%s" target="_blank">RFC9463</a>').format('https://www.rfc-editor.org/rfc/rfc9463')+'<br/>'+
_('IPv4 addresses are only supported if <code>odhcpd</code> also handles DHCPv4.')+'<br/>'+
_('Syntax: <code>&lt;numeric priority&gt; &lt;domain-name&gt; [IP,...] [SVC parameter ...]</code>')+'<br/>'+
_('Example: <code>100 dns.example.com 2001:db8::53,192.168.1.53 alpn=doq port=853</code>')+'<br/>'+
_('Note: the <code>_lifetime=&lt;seconds&gt;</code> SVC parameter sets the lifetime of the announced server (use <code>0</code> to indicate a server which should no longer be used).'));so.datatype='string';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so.depends('dhcpv6','server');so.depends({dhcpv6:'hybrid',master:'0'});so=ss.taboption('ipv6',form.DynamicList,'domain',_('Announce DNS domains'),_('Specifies a fixed list of DNS search domains to announce via DHCPv6.'));so.datatype='hostname';so.depends('ra','server');so.depends({ra:'hybrid',master:'0'});so.depends('dhcpv6','server');so.depends({dhcpv6:'hybrid',master:'0'});so=ss.taboption('dhcpv6',form.DynamicList,'ntp',_('NTP Servers'),_('DHCPv6 option 56. %s.','DHCPv6 option 56. RFC5908 link').format('<a href="%s" target="_blank">RFC5908</a>').format('https://www.rfc-editor.org/rfc/rfc5908#section-4'));so.datatype='host(0)';for(let x of uci.get('system','ntp','server')||''){so.value(x);}
const local_nets=this.networks.filter(function(n){return n.getName()!='loopback'});if(local_nets){if(uci.get('system','ntp','enable_server')){local_nets.forEach(function(n){n.getIPAddrs().forEach(function(i4){so.value(i4.split('/')[0]);});n.getIP6Addrs().forEach(function(i6){so.value(i6.split('/')[0]);});});}}
so.optional=true;so.rmempty=true;so.depends('dhcpv6','server');so.depends({dhcpv6:'hybrid',master:'0'});so=ss.taboption('ipv6',form.RichListValue,'ndp',_('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),_('Configures the operation mode of the NDP proxy service on this interface.'));so.optional=true;so.value('',_('disabled'),_('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));so.value('relay',_('relay mode'),_('Forward <abbr title="Neighbour Discovery Protocol">NDP</abbr> <abbr title="Neighbour Solicitation, Type 135">NS</abbr> and <abbr title="Neighbour Advertisement, Type 136">NA</abbr> messages between the designated master interface and downstream interfaces.'));so.value('hybrid',_('hybrid mode'),' ');so=ss.taboption('ipv6',form.Flag,'ndproxy_routing',_('Learn routes'),_('Set up routes for proxied IPv6 neighbours.'));so.default=so.enabled;so.depends('ndp','relay');so.depends('ndp','hybrid');so=ss.taboption('ipv6',form.Flag,'ndproxy_slave',_('NDP-Proxy slave'),_('Set interface as NDP-Proxy external slave. Default is off.'));so.depends({ndp:'relay',master:'0'});so.depends({ndp:'hybrid',master:'0'});}
ifc.renderFormOptions(s);o=nettools.replaceOption(s,'advanced',form.Flag,'defaultroute',_('Use default gateway'),_('If unchecked, no default route is configured'));o.default=o.enabled;if(has_peerdns(protoval)){o=nettools.replaceOption(s,'advanced',form.Flag,'peerdns',_('Use DNS servers advertised by peer'),_('If unchecked, the advertised DNS server addresses are ignored'));o.default=o.enabled;}
o=nettools.replaceOption(s,'advanced',form.DynamicList,'dns',_('Use custom DNS servers'));if(has_peerdns(protoval))
o.depends('peerdns','0');o.datatype='ipaddr';o=nettools.replaceOption(s,'advanced',form.Value,'dns_metric',_('DNS weight'),_('The DNS server entries in the local resolv.conf are primarily sorted by the weight specified here'));o.datatype='uinteger';o.placeholder='0';o=nettools.replaceOption(s,'advanced',form.Value,'metric',_('Use gateway metric'),_('Metric is an ordinal, where a gateway with 1 is chosen 1st, 2 is chosen 2nd, 3 is chosen 3rd, etc'));o.datatype='uinteger';o.placeholder='0';if(L.hasSystemFeature('mptcp')){o=nettools.replaceOption(s,'advanced',form.RichListValue,'multipath',_('Multi-Path TCP'),_('Multi-Path TCP')+' %s'.format('<a href="%s" target="_blank">RFC8684</a>').format('https://www.rfc-editor.org/rfc/rfc8684.html')+'<br/>'+
_('For packets originating from this device, e.g. VPN.'));o.value('off',_('Off'),_('Disables this interface for MPTCP'));o.value('on',_('On'),_('No special configuration'));o.value('master',_('Master'),_('Sets default route for all traffic'));o.value('backup',_('Backup'),_('Hot standby; use this interface; do not forward traffic until no other interface is available (faster)'));o.value('handover',_('Handover'),_('Cold standby; Establish a connection only when no other interface is available (slower)'));}
o=nettools.replaceOption(s,'advanced',form.Value,'ip4table',_('Override IPv4 routing table'));o.datatype='or(uinteger, string)';for(let rt of rtTables)
o.value(rt[1],'%s (%d)'.format(rt[1],rt[0]));o=nettools.replaceOption(s,'advanced',form.Value,'ip6table',_('Override IPv6 routing table'));o.datatype='or(uinteger, string)';for(let rt of rtTables)
o.value(rt[1],'%s (%d)'.format(rt[1],rt[0]));if(has_sourcefilter(protoval)){o=nettools.replaceOption(s,'advanced',form.Flag,'sourcefilter',_('IPv6 source routing'),_('Automatically handle multiple uplink interfaces using source-based policy routing.'));o.default=o.enabled;}
o=nettools.replaceOption(s,'advanced',form.Flag,'delegate',_('Delegate IPv6 prefixes'),_('Enable downstream delegation of IPv6 prefixes available on this interface'));o.default=o.enabled;o=nettools.replaceOption(s,'advanced',form.Value,'ip6assign',_('IPv6 assignment length'),_('Assign a part of given length of every public IPv6-prefix to this interface'));o.value('',_('disabled'));o.value('64');o.datatype='max(128)';o=nettools.replaceOption(s,'advanced',form.Value,'ip6hint',_('IPv6 assignment hint'),_('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));o.placeholder='0';o.validate=function(section_id,value){if(value==null||value=='')
return true;const n=parseInt(value,16);if(!/^(0x)?[0-9a-fA-F]+$/.test(value)||isNaN(n)||n>=0xffffffff)
return _('Expecting a hexadecimal assignment hint');return true;};for(let i=33;i<=64;i++)
o.depends('ip6assign',String(i));o=nettools.replaceOption(s,'advanced',form.DynamicList,'ip6class',_('IPv6 prefix filter'),_('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));o.value('local','local (%s)'.format(_('Local ULA')));const prefixClasses={};this.networks.forEach(function(net){const prefixes=net._ubus('ipv6-prefix');if(Array.isArray(prefixes)){prefixes.forEach(function(pfx){if(L.isObject(pfx)&&typeof(pfx['class'])=='string'){prefixClasses[pfx['class']]=prefixClasses[pfx['class']]||{};prefixClasses[pfx['class']][net.getName()]=true;}});}});Object.keys(prefixClasses).sort().forEach(function(c){const networks=Object.keys(prefixClasses[c]).sort().join(', ');o.value(c,(c!=networks)?'%s (%s)'.format(c,networks):c);});o=nettools.replaceOption(s,'advanced',form.Value,'ip6ifaceid',_('IPv6 suffix'),_("Optional. Allowed values: 'eui64', 'random', fixed value like '::1' or '::1:2'. When IPv6 prefix (like 'a:b:c:d::') is received from a delegating server, use the suffix (like '::1') to form the IPv6 address ('a:b:c:d::1') for the interface."));o.datatype='ip6hostid';o.placeholder='::1';o=nettools.replaceOption(s,'advanced',form.Value,'ip6weight',_('IPv6 preference'),_('When delegating prefixes to multiple downstreams, interfaces with a higher preference value are considered first when allocating subnets.'));o.datatype='uinteger';o.placeholder='0';for(let o of s.children){const deps=[];switch(o.option){case'proto':case'auto':case'_dhcp':case'_zone':case'_switch_proto':case'_ifacestat_modal':continue;case'igmp_snooping':case'stp':case'type':case'_net_device':for(let p of protocols){if(!p.isVirtual()){if(o.deps.length)
for(let od of o.deps)
deps.push(Object.assign({proto:p.getProtocol()},od));else
deps.push({proto:p.getProtocol()});}}
o.deps=deps;break;default:if(o.deps.length)
for(let od of o.deps)
od.proto=protoval;else
o.depends('proto',protoval);}}
this.activeSection=s.section;},this));};s.handleModalCancel=function(){const type=uci.get('network',this.activeSection||this.addedSection,'type');const device=(type=='bridge')?'br-%s'.format(this.activeSection||this.addedSection):null;uci.sections('network','bridge-vlan',function(bvs){if(device!=null&&bvs.device==device)
uci.remove('network',bvs['.name']);});return form.GridSection.prototype.handleModalCancel.apply(this,arguments);};s.handleAdd=function(ev){const m2=new form.Map('network');const s2=m2.section(form.NamedSection,'_new_');const protocols=network.getProtocols();let proto,name,device;protocols.sort(function(a,b){return L.naturalCompare(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');if(value.length>15)
return _('The interface name is too long');return true;};proto=s2.option(form.ListValue,'proto',_('Protocol'));proto.onchange=function(ev,section_id,value){const elem=name.getUIElement(section_id);elem.triggerValidation();};device=s2.option(widgets.DeviceSelect,'device',_('Device'));device.noaliases=false;device.optional=false;for(let p of protocols){proto.value(p.getProtocol(),p.getI18n());if(!p.isVirtual())
device.depends('proto',p.getProtocol());}
m2.render().then(L.bind(function(nodes){ui.showModal(_('Add new interface...'),[nodes,E('div',{'class':'right'},[E('button',{'class':'btn','click':ui.hideModal},_('Cancel')),' ',E('button',{'class':'cbi-button cbi-button-positive important','click':ui.createHandlerFn(this,function(ev){const nameval=name.isValid('_new_')?name.formvalue('_new_'):null;const protoval=proto.isValid('_new_')?proto.formvalue('_new_'):null;const protoclass=protoval?network.getProtocol(protoval,nameval):null;if(nameval==null||protoval==null||nameval==''||protoval=='')
return;return protoclass.isCreateable(nameval).then(function(checkval){if(checkval!=null){ui.addNotification(null,E('p',_('New interface for "%s" can not be created: %s').format(protoclass.getI18n(),checkval)));ui.hideModal();return;}
return m.save(function(){const section_id=uci.add('network','interface',nameval);protoclass.set('proto',protoval);protoclass.addDevice(device.formvalue('_new_'));m.children[0].addedSection=section_id;ui.hideModal();ui.showModal(null,E('p',{'class':'spinning'},[_('Loading data…')]));}).then(L.bind(m.children[0].renderMoreOptionsModal,m.children[0],nameval));});})},_('Create interface'))])],'cbi-modal');nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();},this));};s.handleRemove=function(section_id,ev){return network.deleteNetwork(section_id).then(L.bind(function(section_id,ev){return form.GridSection.prototype.handleRemove.apply(this,[section_id,ev]);},this,section_id,ev));};o=s.option(form.DummyValue,'_ifacebox');o.modalonly=false;o.textvalue=function(section_id){const net=this.section.networks.filter(function(n){return n.getName()==section_id})[0];const zone=net?this.section.zones.filter(function(z){return!!z.getNetworks().filter(function(n){return n==section_id})[0]})[0]:null;if(!net)
return;const node=E('div',{'class':'ifacebox'},[E('div',{'class':'ifacebox-head','style':firewall.getZoneColorStyle(zone),'title':zone?_('Part of zone %q').format(zone.getName()):_('No zone assigned')},E('strong',net.getName())),E('div',{'class':'ifacebox-body','id':'%s-ifc-devices'.format(section_id),'data-network':section_id},[E('img',{'src':L.resource('icons/ethernet_disabled.svg'),'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){const net=this.section.networks.filter(function(n){return n.getName()==section_id})[0];if(!net)
return;const 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.defaults={'1':[{proto:'static'}],'0':[]};s=m.section(form.GridSection,'device',_('Devices'));s.addremove=true;s.anonymous=true;s.addbtntitle=_('Add device configuration…');s.cfgsections=function(){const sections=uci.sections('network','device');const section_ids=sections.sort(function(a,b){return L.naturalCompare(a.name,b.name)}).map(function(s){return s['.name']});for(let nd of netDevs){if(sections.filter(function(s){return s.name==nd.getName()}).length)
continue;if(nd.getType()=='wifi'&&!nd.isUp())
continue;const m=nd.isBridge()?nd.getName().match(/^br-([A-Za-z0-9_]+)$/):null;const s=m?uci.get('network',m[1]):null;if(s&&s['.type']=='interface'&&s.type=='bridge')
continue;section_ids.push('dev:%s'.format(nd.getName()));}
return section_ids;};s.renderMoreOptionsModal=function(section_id,ev){const m=section_id.match(/^dev:(.+)$/);if(m){const devtype=getDevType(section_id);section_id=uci.add('network','device');uci.set('network',section_id,'name',m[1]);uci.set('network',section_id,'type',(devtype!='ethernet')?devtype:null);this.addedSection=section_id;}
return this.super('renderMoreOptionsModal',[section_id,ev]);};s.renderRowActions=function(section_id){const trEl=this.super('renderRowActions',[section_id,_('Configure…')]);const deleteBtn=trEl.querySelector('button:last-child');deleteBtn.firstChild.data=_('Unconfigure');deleteBtn.setAttribute('title',_('Remove related device settings from the configuration'));deleteBtn.disabled=section_id.match(/^dev:/)?true:null;return trEl;};s.modaltitle=function(section_id){const m=section_id.match(/^dev:(.+)$/);const name=m?m[1]:uci.get('network',section_id,'name');return name?'%s: %q'.format(getDevTypeDesc(section_id),name):_('Add device configuration');};s.addModalOptions=function(s){const isNew=(uci.get('network',s.section,'name')==null);const dev=getDevice(s.section);const devName=dev?dev.getName():null;if(devName){return L.resolveDefault(callNetworkDeviceStatus(devName),{}).then((status)=>{const hasPSE=(status.pse!=null);nettools.addDeviceOptions(s,dev,isNew,rtTables,hasPSE);});}else{nettools.addDeviceOptions(s,dev,isNew,rtTables,false);return Promise.resolve();}};s.handleModalCancel=function(map){const name=uci.get('network',this.addedSection,'name');uci.sections('network','bridge-vlan',function(bvs){if(name!=null&&bvs.device==name)
uci.remove('network',bvs['.name']);});if(map.addedVLANs)
for(let mav of map.addedVLANs)
uci.remove('network',mav);if(this.addedSection)
uci.remove('network',this.addedSection);return form.GridSection.prototype.handleModalCancel.apply(this,arguments);};s.handleRemove=function(section_id){const name=uci.get('network',section_id,'name');const type=uci.get('network',section_id,'type');if(name!=null&&type=='bridge'){uci.sections('network','bridge-vlan',function(bvs){if(bvs.device==name)
uci.remove('network',bvs['.name']);});}
return form.GridSection.prototype.handleRemove.apply(this,arguments);};function getDevice(section_id){const m=section_id.match(/^dev:(.+)$/);const name=m?m[1]:uci.get('network',section_id,'name');return netDevs.filter(function(d){return d.getName()==name})[0];}
function getDevType(section_id){const dev=getDevice(section_id);const cfg=uci.get('network',section_id);const type=cfg?(uci.get('network',section_id,'type')||'ethernet'):(dev?dev.getType():'');switch(type){case'':return null;case'vlan':case'8021q':return'8021q';case'8021ad':return'8021ad';case'bonding':return'bonding';case'bridge':return'bridge';case'tunnel':return'tunnel';case'macvlan':return'macvlan';case'veth':return'veth';case'vrf':return'vrf';case'wifi':case'alias':case'switch':case'ethernet':default:return'ethernet';}}
function getDevTypeDesc(section_id){switch(getDevType(section_id)||''){case'':return E('em',[_('Device not present')]);case'8021q':return _('VLAN (802.1q)');case'8021ad':return _('VLAN (802.1ad)');case'bonding':return _('Aggregation device');case'bridge':return _('Bridge device');case'tunnel':return _('Tunnel device');case'macvlan':return _('MAC VLAN');case'veth':return _('Virtual Ethernet');case'vrf':return _('Virtual Routing and Forwarding (VRF)');default:return _('Network device');}}
o=s.option(form.DummyValue,'name',_('Device'));o.modalonly=false;o.textvalue=function(section_id){const dev=getDevice(section_id);const ext=section_id.match(/^dev:/);const icon=render_iface(dev);if(ext)
icon.querySelector('img').style.opacity='.5';return E('span',{'class':'ifacebadge'},[icon,E('span',{'style':ext?'opacity:.5':null},[dev?dev.getName():(uci.get('network',section_id,'name')||'?')])]);};o=s.option(form.DummyValue,'type',_('Type'));o.textvalue=getDevTypeDesc;o.modalonly=false;o=s.option(form.DummyValue,'macaddr',_('MAC Address'));o.modalonly=false;o.textvalue=function(section_id){const dev=getDevice(section_id);const val=uci.get('network',section_id,'macaddr');const mac=dev?dev.getMAC():null;return val?E('strong',{'data-tooltip':_('The value is overridden by configuration.')},[val.toUpperCase()]):(mac||'-');};o=s.option(form.DummyValue,'mtu',_('MTU'));o.modalonly=false;o.textvalue=function(section_id){const dev=getDevice(section_id);const val=uci.get('network',section_id,'mtu');const mtu=dev?dev.getMTU():null;return val?E('strong',{'data-tooltip':_('The value is overridden by configuration.')},[val]):(mtu||'-').toString();};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'),_('Unique Local Address (%s) - prefix <code>fd00::/8</code> (the L bit is always 1).').format('<a href="%s" target="_blank">RFC4193</a>').format('https://datatracker.ietf.org/doc/html/rfc4193#section-3')+' '+
_('ULA for IPv6 is analogous to IPv4 private network addressing.')+' '+
_('This prefix is randomly generated at first install.'));o.datatype='cidr6';o=s.option(form.Value,'dhcp_default_duid',_('Default DUID'),_('The default <abbr title="DHCP Unique Identifier">DUID</abbr> for this device, used when acting as a DHCP server or client. The client identifier can be overridden on a per-interface basis.')+'<br />'+
_('This identifier is randomly generated the first time the device is booted.'));o.datatype='and(rangelength(6,260),hexstring)';if(L.hasSystemFeature('mptcp')){o=s.option(form.Flag,'multipath',_('Multi-Path TCP'),_('For packets originating from this device, e.g. VPN.'));o.optional=true;}
const l3mdevhelp1=_('%s services running on this device in the default VRF context (ie., not bound to any VRF device) shall work across all VRF domains.');const l3mdevhelp2=_('Off means VRF traffic will be handled exclusively by sockets bound to VRFs.');o=s.option(form.Flag,'tcp_l3mdev',_('TCP Layer 3 Master Device (tcp_l3mdev) accept'),l3mdevhelp1.format('TCP')+'<br/>'+
l3mdevhelp2);o=s.option(form.Flag,'udp_l3mdev',_('UDP Layer 3 Master Device (udp_l3mdev) accept'),l3mdevhelp1.format('UDP')+'<br/>'+
l3mdevhelp2);o=s.option(form.ListValue,'packet_steering',_('Packet Steering'),_('Enable packet steering across CPUs. May help or hinder network speed.'));o.value('0',_('Disabled'));o.value('1',_('Enabled'));o.value('2',_('Enabled (all CPUs)'));o.default='1';o.optional=true;const steer_flow=uci.get('network','globals','steering_flows');o=s.option(form.Value,'steering_flows',_('Steering flows (<abbr title="Receive Packet Steering">RPS</abbr>)'),_('Directs packet flows to specific CPUs where the local socket owner listens (the local service).')+' '+
_('Note: this setting is for local services on the device only (not for forwarding).'));o.value('',_('Standard: none'));o.value('128',_('Suggested: 128'));o.value('256',_('256'));o.depends('packet_steering','1');o.depends('packet_steering','2');o.datatype='uinteger';o.default=steer_flow;if(dslModemType!=null){s=m.section(form.TypedSection,'dsl',_('DSL'));s.anonymous=true;o=s.option(form.ListValue,'annex',_('Annex'));if(dslModemType=='vdsl'){o.value('a',_('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));o.value('b',_('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));o.value('j',_('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));}else{o.value('a',_('ADSL (all variants) Annex A/L/M'));o.value('b',_('ADSL (all variants) Annex B'));o.value('j',_('ADSL (all variants) Annex B/J'));}
o.value('m',_('ADSL (all variants) Annex M'));o.value('at1',_('ANSI T1.413'));o.value('admt',_('ADSL (G.992.1) Annex A'));o.value('bdmt',_('ADSL (G.992.1) Annex B'));o.value('alite',_('Splitterless ADSL (G.992.2) Annex A'));o.value('a2',_('ADSL2 (G.992.3) Annex A'));o.value('b2',_('ADSL2 (G.992.3) Annex B'));o.value('l',_('ADSL2 (G.992.3) Annex L'));o.value('m2',_('ADSL2 (G.992.3) Annex M'));o.value('a2p',_('ADSL2+ (G.992.5) Annex A'));o.value('b2p',_('ADSL2+ (G.992.5) Annex B'));o.value('m2p',_('ADSL2+ (G.992.5) Annex M'));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(let i=-100;i<=100;i+=5)
o.value(i,_('%.1f dB').format(i/10));}
s.option(form.Value,'firmware',_('Firmware File'));}
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){const sections=uci.sections('network','atm-bridge');let max_unit=-1;for(let s of sections){const unit=+s.unit;if(!isNaN(unit)&&unit>max_unit)
max_unit=unit;}
return this.map.save(function(){const sid=uci.add('network','atm-bridge');uci.set('network',sid,'unit',max_unit+1);uci.set('network',sid,'atmdev',0);uci.set('network',sid,'encaps','llc');uci.set('network',sid,'payload','bridged');uci.set('network',sid,'vci',35);uci.set('network',sid,'vpi',8);});};s.tab('general',_('General Setup'));s.tab('advanced',_('Advanced Settings'));o=s.taboption('general',form.Value,'vci',_('ATM Virtual Channel Identifier (VCI)'));s.taboption('general',form.Value,'vpi',_('ATM Virtual Path Identifier (VPI)'));o=s.taboption('general',form.ListValue,'encaps',_('Encapsulation mode'));o.value('llc',_('LLC'));o.value('vc',_('VC-Mux'));s.taboption('advanced',form.Value,'atmdev',_('ATM device number'));s.taboption('advanced',form.Value,'unit',_('Bridge unit number'));o=s.taboption('advanced',form.ListValue,'payload',_('Forwarding mode'));o.value('bridged',_('bridged'));o.value('routed',_('routed'));}
return m.render().then(L.bind(function(m,nodes){poll.add(L.bind(function(){const section_ids=m.children[0].cfgsections();const tasks=[];for(let sid of section_ids){const row=nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(sid));const dsc=row.querySelector('[data-name="_ifacestat"] > div');const btn1=row.querySelector('.cbi-section-actions .reconnect');const btn2=row.querySelector('.cbi-section-actions .down');if(dsc.getAttribute('reconnect')==''){dsc.setAttribute('reconnect','1');tasks.push(fs.exec('/sbin/ifup',[sid]).catch(function(e){ui.addNotification(null,E('p',e.message));}));}
else if(dsc.getAttribute('disconnect')==''){dsc.setAttribute('disconnect','1');tasks.push(fs.exec('/sbin/ifdown',[sid]).catch(function(e){ui.addNotification(null,E('p',e.message));}));}
else if(dsc.getAttribute('reconnect')=='1'){dsc.removeAttribute('reconnect');btn1.classList.remove('spinning');btn1.disabled=false;}
else if(dsc.getAttribute('disconnect')=='1'){dsc.removeAttribute('disconnect');btn2.classList.remove('spinning');btn2.disabled=false;}}
return Promise.all(tasks).then(L.bind(network.getNetworks,network)).then(L.bind(this.poll_status,this,nodes));},this),5);return nodes;},this,m));}});