'use strict';'require validation';'require baseclass';'require request';'require session';'require poll';'require dom';'require rpc';'require uci';'require fs';let modalDiv=null;let tooltipDiv=null;let indicatorDiv=null;let tooltipTimeout=null;const UIElement=baseclass.extend({getValue(){if(dom.matches(this.node,'select')||dom.matches(this.node,'input'))
return this.node.value;return null;},setValue(value){if(dom.matches(this.node,'select')||dom.matches(this.node,'input'))
this.node.value=value;},setPlaceholder(value){const node=this.node?this.node.querySelector('input,textarea'):null;if(node){switch(node.getAttribute('type')??'text'){case'password':case'search':case'tel':case'text':case'url':if(value!=null&&value!='')
node.setAttribute('placeholder',value);else
node.removeAttribute('placeholder');}}},isChanged(){return(this.node?this.node.getAttribute('data-changed'):null)=='true';},isValid(){return(this.validState!==false);},getValidationError(){return this.validationError??'';},triggerValidation(){if(typeof(this.vfunc)!='function')
return false;const wasValid=this.isValid();this.vfunc();return(wasValid!=this.isValid());},registerEvents(targetNode,synevent,events){const dispatchFn=L.bind((ev)=>{this.node.dispatchEvent(new CustomEvent(synevent,{bubbles:true}));},this);for(let i=0;i<events.length;i++)
targetNode.addEventListener(events[i],dispatchFn);},setUpdateEvents(targetNode,...events){const datatype=this.options.datatype;const optional=this.options.hasOwnProperty('optional')?this.options.optional:true;const validate=this.options.validate;this.registerEvents(targetNode,'widget-update',events);if(!datatype&&!validate)
return;this.vfunc=UI.prototype.addValidator(...[targetNode,datatype??'string',optional,validate].concat(events));this.node.addEventListener('validation-success',L.bind((ev)=>{this.validState=true;this.validationError='';},this));this.node.addEventListener('validation-failure',L.bind((ev)=>{this.validState=false;this.validationError=ev.detail.message;},this));},setChangeEvents(targetNode,...events){const tag_changed=L.bind(function(ev){this.setAttribute('data-changed',true)},this.node);for(let i=0;i<events.length;i++)
targetNode.addEventListener(events[i],tag_changed);this.registerEvents(targetNode,'widget-change',events);},render(){}});const UITextfield=UIElement.extend({__init__(value,options){this.value=value;this.options=Object.assign({optional:true,password:false},options);},render(){const frameEl=E('div',{'id':this.options.id});const inputEl=E('input',{'id':this.options.id?`widget.${this.options.id}`:null,'name':this.options.name,'type':'text','class':`password-input ${this.options.password ? 'cbi-input-password' : 'cbi-input-text'}`,'readonly':this.options.readonly?'':null,'disabled':this.options.disabled?'':null,'maxlength':this.options.maxlength,'placeholder':this.options.placeholder,'value':this.value,});if(this.options.password){frameEl.appendChild(E('div',{'class':'control-group'},[inputEl,E('button',{'class':'cbi-button cbi-button-neutral','title':_('Reveal/hide password'),'aria-label':_('Reveal/hide password'),'click':function(ev){const e=this.parentElement.querySelector('input.password-input')
if(e){e.type=(e.type==='password')?'text':'password';}else{console.error('unable to find input corresponding to reveal/hide button');}
ev.preventDefault();}},'∗')]));window.requestAnimationFrame(()=>{inputEl.type='password'});}
else{frameEl.appendChild(inputEl);}
return this.bind(frameEl);},bind(frameEl){const inputEl=frameEl.querySelector('input');this.node=frameEl;this.setUpdateEvents(inputEl,'keyup','blur');this.setChangeEvents(inputEl,'change');dom.bindClassInstance(frameEl,this);return frameEl;},getValue(){const inputEl=this.node.querySelector('input');return inputEl.value;},setValue(value){const inputEl=this.node.querySelector('input');inputEl.value=value;}});const UITextarea=UIElement.extend({__init__(value,options){this.value=value;this.options=Object.assign({optional:true,wrap:false,cols:null,rows:null},options);},render(){const style=!this.options.cols?'width:100%':null;const frameEl=E('div',{'id':this.options.id,'style':style});const value=(this.value!=null)?String(this.value):'';frameEl.appendChild(E('textarea',{'id':this.options.id?`widget.${this.options.id}`:null,'name':this.options.name,'class':'cbi-input-textarea','readonly':this.options.readonly?'':null,'disabled':this.options.disabled?'':null,'placeholder':this.options.placeholder,'style':style,'cols':this.options.cols,'rows':this.options.rows,'wrap':this.options.wrap?'soft':'off'},[value]));if(this.options.monospace)
frameEl.firstElementChild.style.fontFamily='monospace';return this.bind(frameEl);},bind(frameEl){const inputEl=frameEl.firstElementChild;this.node=frameEl;this.setUpdateEvents(inputEl,'keyup','blur');this.setChangeEvents(inputEl,'change');dom.bindClassInstance(frameEl,this);return frameEl;},getValue(){return this.node.firstElementChild.value;},setValue(value){this.node.firstElementChild.value=value;}});const UICheckbox=UIElement.extend({__init__(value,options){this.value=value;this.options=Object.assign({value_enabled:'1',value_disabled:'0'},options);},render(){const id='cb%08x'.format(Math.random()*0xffffffff);const frameEl=E('div',{'id':this.options.id,'class':'cbi-checkbox'});if(this.options.hiddenname)
frameEl.appendChild(E('input',{'type':'hidden','name':this.options.hiddenname,'value':1}));frameEl.appendChild(E('input',{'id':id,'name':this.options.name,'type':'checkbox','value':this.options.value_enabled,'checked':(this.value==this.options.value_enabled)?'':null,'disabled':this.options.disabled?'':null,'data-widget-id':this.options.id?`widget.${this.options.id}`:null}));frameEl.appendChild(E('label',{'for':id}));if(this.options.tooltip!=null){let icon="⚠️";if(this.options.tooltipicon!=null)
icon=this.options.tooltipicon;frameEl.appendChild(E('label',{'class':'cbi-tooltip-container'},[icon,E('div',{'class':'cbi-tooltip'},this.options.tooltip)]));}
return this.bind(frameEl);},bind(frameEl){this.node=frameEl;const input=frameEl.querySelector('input[type="checkbox"]');this.setUpdateEvents(input,'click','blur');this.setChangeEvents(input,'change');dom.bindClassInstance(frameEl,this);return frameEl;},isChecked(){return this.node.querySelector('input[type="checkbox"]').checked;},getValue(){return this.isChecked()?this.options.value_enabled:this.options.value_disabled;},setValue(value){this.node.querySelector('input[type="checkbox"]').checked=(value==this.options.value_enabled);}});const UISelect=UIElement.extend({__init__(value,choices,options){if(!L.isObject(choices))
choices={};if(!Array.isArray(value))
value=(value!=null&&value!='')?[value]:[];if(!options.multiple&&value.length>1)
value.length=1;this.values=value;this.choices=choices;this.options=Object.assign({multiple:false,widget:'select',orientation:'horizontal'},options);if(this.choices.hasOwnProperty(''))
this.options.optional=true;},render(){const frameEl=E('div',{'id':this.options.id});let keys=Object.keys(this.choices);if(this.options.sort===true)
keys.sort(L.naturalCompare);else if(Array.isArray(this.options.sort))
keys=this.options.sort;if(this.options.widget!='radio'&&this.options.widget!='checkbox'){frameEl.appendChild(E('select',{'id':this.options.id?`widget.${this.options.id}`:null,'name':this.options.name,'size':this.options.size,'class':'cbi-input-select','multiple':this.options.multiple?'':null,'disabled':this.options.disabled?'':null}));if(this.options.optional)
frameEl.lastChild.appendChild(E('option',{'value':'','selected':(this.values.length==0||this.values[0]=='')?'':null},[this.choices['']??this.options.placeholder??_('-- Please choose --')]));for(let i=0;i<keys.length;i++){if(keys[i]==null||keys[i]=='')
continue;frameEl.lastChild.appendChild(E('option',{'value':keys[i],'selected':(this.values.indexOf(keys[i])>-1)?'':null},[this.choices[keys[i]]??keys[i]]));}}
else{const brEl=(this.options.orientation==='horizontal')?document.createTextNode(' \xa0 '):E('br');for(let i=0;i<keys.length;i++){frameEl.appendChild(E('span',{'class':'cbi-%s'.format(this.options.multiple?'checkbox':'radio')},[E('input',{'id':this.options.id?'widget.%s.%d'.format(this.options.id,i):null,'name':this.options.id??this.options.name,'type':this.options.multiple?'checkbox':'radio','class':this.options.multiple?'cbi-input-checkbox':'cbi-input-radio','value':keys[i],'checked':(this.values.indexOf(keys[i])>-1)?'':null,'disabled':this.options.disabled?'':null}),E('label',{'for':this.options.id?'widget.%s.%d'.format(this.options.id,i):null}),E('span',{'click':function(ev){ev.currentTarget.previousElementSibling.previousElementSibling.click();}},[this.choices[keys[i]]??keys[i]])]));frameEl.appendChild(brEl.cloneNode());}}
return this.bind(frameEl);},bind(frameEl){this.node=frameEl;if(this.options.widget!='radio'&&this.options.widget!='checkbox'){this.setUpdateEvents(frameEl.firstChild,'change','click','blur');this.setChangeEvents(frameEl.firstChild,'change');}
else{const radioEls=frameEl.querySelectorAll('input[type="radio"]');for(let i=0;i<radioEls.length;i++){this.setUpdateEvents(radioEls[i],'change','click','blur');this.setChangeEvents(radioEls[i],'change','click','blur');}}
dom.bindClassInstance(frameEl,this);return frameEl;},getValue(){if(this.options.widget!='radio'&&this.options.widget!='checkbox')
return this.node.firstChild.value;const radioEls=this.node.querySelectorAll('input[type="radio"]');for(let i=0;i<radioEls.length;i++)
if(radioEls[i].checked)
return radioEls[i].value;return null;},setValue(value){if(this.options.widget!='radio'&&this.options.widget!='checkbox'){if(value==null)
value='';for(let i=0;i<this.node.firstChild.options.length;i++)
this.node.firstChild.options[i].selected=(this.node.firstChild.options[i].value==value);return;}
const radioEls=frameEl.querySelectorAll('input[type="radio"]');for(let i=0;i<radioEls.length;i++)
radioEls[i].checked=(radioEls[i].value==value);}});const UIDropdown=UIElement.extend({__init__(value,choices,options){if(typeof(choices)!='object')
choices={};if(!Array.isArray(value))
this.values=(value!=null&&value!='')?[value]:[];else
this.values=value;this.choices=choices;this.options=Object.assign({sort:true,multiple:Array.isArray(value),optional:true,select_placeholder:_('-- Please choose --'),custom_placeholder:_('-- custom --'),display_items:3,dropdown_items:-1,create:false,create_query:'.create-item-input',create_template:'script[type="item-template"]'},options);},render(){const sb=E('div',{'id':this.options.id,'class':'cbi-dropdown','multiple':this.options.multiple?'':null,'optional':this.options.optional?'':null,'disabled':this.options.disabled?'':null,'tabindex':-1},E('ul'));let keys=Object.keys(this.choices);if(this.options.sort===true)
keys.sort(L.naturalCompare);else if(Array.isArray(this.options.sort))
keys=this.options.sort;if(this.options.create)
for(let i=0;i<this.values.length;i++)
if(!this.choices.hasOwnProperty(this.values[i]))
keys.push(this.values[i]);for(let i=0;i<keys.length;i++){let label=this.choices[keys[i]];if(dom.elem(label))
label=label.cloneNode(true);sb.lastElementChild.appendChild(E('li',{'data-value':keys[i],'selected':(this.values.indexOf(keys[i])>-1)?'':null},[label??keys[i]]));}
if(this.options.create){const createEl=E('input',{'type':'text','class':'create-item-input','readonly':this.options.readonly?'':null,'maxlength':this.options.maxlength,'placeholder':this.options.custom_placeholder??this.options.placeholder,'inputmode':'text','enterkeyhint':'done'});if(this.options.datatype||this.options.validate)
UI.prototype.addValidator(createEl,this.options.datatype??'string',true,this.options.validate,'blur','keyup');sb.lastElementChild.appendChild(E('li',{'data-value':'-'},createEl));}
if(this.options.create_markup)
sb.appendChild(E('script',{type:'item-template'},this.options.create_markup));return this.bind(sb);},bind(sb){const o=this.options;o.multiple=sb.hasAttribute('multiple');o.optional=sb.hasAttribute('optional');o.placeholder=sb.getAttribute('placeholder')??o.placeholder;o.display_items=parseInt(sb.getAttribute('display-items')??o.display_items);o.dropdown_items=parseInt(sb.getAttribute('dropdown-items')??o.dropdown_items);o.create_query=sb.getAttribute('item-create')??o.create_query;o.create_template=sb.getAttribute('item-template')??o.create_template;const ul=sb.querySelector('ul');const more=sb.appendChild(E('span',{class:'more',tabindex:-1},'···'));const open=sb.appendChild(E('span',{class:'open',tabindex:-1},'▾'));const canary=sb.appendChild(E('div'));const create=sb.querySelector(this.options.create_query);let ndisplay=this.options.display_items;let n=0;if(this.options.multiple){let items=ul.querySelectorAll('li');for(let i=0;i<items.length;i++){this.transformItem(sb,items[i]);if(items[i].hasAttribute('selected')&&ndisplay-->0)
items[i].setAttribute('display',n++);}}
else{if(this.options.optional&&!ul.querySelector('li[data-value=""]')){const placeholder=E('li',{placeholder:''},this.options.select_placeholder??this.options.placeholder);ul.firstChild?ul.insertBefore(placeholder,ul.firstChild):ul.appendChild(placeholder);}
let items=ul.querySelectorAll('li');const sel=sb.querySelectorAll('[selected]');sel.forEach(s=>{s.removeAttribute('selected');});const s=sel[0]??items[0];if(s){s.setAttribute('selected','');s.setAttribute('display',n++);}
ndisplay--;}
this.saveValues(sb,ul);ul.setAttribute('tabindex',-1);sb.setAttribute('tabindex',0);if(ndisplay<0)
sb.setAttribute('more','')
else
sb.removeAttribute('more');if(ndisplay==this.options.display_items)
sb.setAttribute('empty','')
else
sb.removeAttribute('empty');dom.content(more,(ndisplay==this.options.display_items)?(this.options.select_placeholder??this.options.placeholder):'···');sb.addEventListener('click',this.handleClick.bind(this));sb.addEventListener('keydown',this.handleKeydown.bind(this));sb.addEventListener('cbi-dropdown-close',this.handleDropdownClose.bind(this));sb.addEventListener('cbi-dropdown-select',this.handleDropdownSelect.bind(this));if('ontouchstart'in window){sb.addEventListener('touchstart',ev=>ev.stopPropagation());window.addEventListener('touchstart',this.closeAllDropdowns);}
else{sb.addEventListener('focus',this.handleFocus.bind(this));canary.addEventListener('focus',this.handleCanaryFocus.bind(this));window.addEventListener('click',this.closeAllDropdowns);}
if(create){create.addEventListener('keydown',this.handleCreateKeydown.bind(this));create.addEventListener('focus',this.handleCreateFocus.bind(this));create.addEventListener('blur',this.handleCreateBlur.bind(this));const li=findParent(create,'li');li.setAttribute('unselectable','');li.addEventListener('click',this.handleCreateClick.bind(this));}
this.node=sb;this.setUpdateEvents(sb,'cbi-dropdown-open','cbi-dropdown-close');this.setChangeEvents(sb,'cbi-dropdown-change','cbi-dropdown-close');dom.bindClassInstance(sb,this);return sb;},getScrollParent(element){let parent=element;let style=getComputedStyle(element);const excludeStaticParent=(style.position==='absolute');if(style.position==='fixed')
return document.body;while((parent=parent.parentElement)!=null){style=getComputedStyle(parent);if(excludeStaticParent&&style.position==='static')
continue;if(/(auto|scroll)/.test(style.overflow+style.overflowY+style.overflowX))
return parent;}
return document.body;},openDropdown(sb){const st=window.getComputedStyle(sb,null);const ul=sb.querySelector('ul');const li=ul.querySelectorAll('li');const fl=findParent(sb,'.cbi-value-field');const sel=ul.querySelector('[selected]');const rect=sb.getBoundingClientRect();const items=Math.min(this.options.dropdown_items,li.length);const scrollParent=this.getScrollParent(sb);document.querySelectorAll('.cbi-dropdown[open]').forEach(s=>{s.dispatchEvent(new CustomEvent('cbi-dropdown-close',{}));});sb.setAttribute('open','');const pv=ul.cloneNode(true);pv.classList.add('preview');if(fl)
fl.classList.add('cbi-dropdown-open');if('ontouchstart'in window){const vpWidth=Math.max(document.documentElement.clientWidth,window.innerWidth??0);const vpHeight=Math.max(document.documentElement.clientHeight,window.innerHeight??0);let start=null;ul.style.top=`${sb.offsetHeight}px`;ul.style.left=`${-rect.left}px`;ul.style.right=`${rect.right - vpWidth}px`;ul.style.maxHeight=`${vpHeight * 0.5}px`;ul.style.WebkitOverflowScrolling='touch';const scrollFrom=scrollParent.scrollTop;const scrollTo=scrollFrom+rect.top-vpHeight*0.5;const scrollStep=timestamp=>{if(!start){start=timestamp;ul.scrollTop=sel?Math.max(sel.offsetTop-sel.offsetHeight,0):0;}
const duration=Math.max(timestamp-start,1);if(duration<100){scrollParent.scrollTop=scrollFrom+(scrollTo-scrollFrom)*(duration/100);window.requestAnimationFrame(scrollStep);}
else{scrollParent.scrollTop=scrollTo;}};window.requestAnimationFrame(scrollStep);}
else{ul.style.maxHeight='1px';ul.style.top=ul.style.bottom='';window.requestAnimationFrame(()=>{const containerRect=scrollParent.getBoundingClientRect();const itemHeight=li[Math.max(0,li.length-2)].getBoundingClientRect().height;let fullHeight=0;const spaceAbove=rect.top-containerRect.top;const spaceBelow=containerRect.bottom-rect.bottom;for(let i=0;i<(items==-1?li.length:items);i++)
fullHeight+=li[i].getBoundingClientRect().height;if(fullHeight<=spaceBelow){ul.style.top=`${rect.height}px`;ul.style.maxHeight=`${spaceBelow}px`;}
else if(fullHeight<=spaceAbove){ul.style.bottom=`${rect.height}px`;ul.style.maxHeight=`${spaceAbove}px`;}
else if(spaceBelow>=spaceAbove){ul.style.top=`${rect.height}px`;ul.style.maxHeight=`${spaceBelow - (spaceBelow % itemHeight)}px`;}
else{ul.style.bottom=`${rect.height}px`;ul.style.maxHeight=`${spaceAbove - (spaceAbove % itemHeight)}px`;}
ul.scrollTop=sel?Math.max(sel.offsetTop-sel.offsetHeight,0):0;});}
const cboxes=ul.querySelectorAll('[selected] input[type="checkbox"]');for(let i=0;i<cboxes.length;i++){cboxes[i].checked=true;cboxes[i].disabled=(cboxes.length==1&&!this.options.optional);}
ul.classList.add('dropdown');sb.insertBefore(pv,ul.nextElementSibling);li.forEach(l=>{if(!l.hasAttribute('unselectable'))
l.setAttribute('tabindex',0);});sb.lastElementChild.setAttribute('tabindex',0);const focusFn=L.bind((el)=>{this.setFocus(sb,el,true);ul.removeEventListener('transitionend',focusFn);},this,sel??li[0]);ul.addEventListener('transitionend',focusFn);},closeDropdown(sb,no_focus){if(!sb.hasAttribute('open'))
return;const pv=sb.querySelector('ul.preview');const ul=sb.querySelector('ul.dropdown');const li=ul.querySelectorAll('li');const fl=findParent(sb,'.cbi-value-field');li.forEach(l=>l.removeAttribute('tabindex'));sb.lastElementChild.removeAttribute('tabindex');sb.removeChild(pv);sb.removeAttribute('open');sb.style.width=sb.style.height='';ul.classList.remove('dropdown');ul.style.top=ul.style.bottom=ul.style.maxHeight='';if(fl)
fl.classList.remove('cbi-dropdown-open');if(!no_focus)
this.setFocus(sb,sb);this.saveValues(sb,ul);},toggleItem(sb,li,force_state){const ul=li.parentNode;if(li.hasAttribute('unselectable'))
return;if(this.options.multiple){const cbox=li.querySelector('input[type="checkbox"]');const items=li.parentNode.querySelectorAll('li');const label=sb.querySelector('ul.preview');let sel=li.parentNode.querySelectorAll('[selected]').length;const more=sb.querySelector('.more');let ndisplay=this.options.display_items;let n=0;if(li.hasAttribute('selected')){if(force_state!==true){if(sel>1||this.options.optional){li.removeAttribute('selected');cbox.checked=cbox.disabled=false;sel--;}
else{cbox.disabled=true;}}}
else{if(force_state!==false){li.setAttribute('selected','');cbox.checked=true;cbox.disabled=false;sel++;}}
while(label&&label.firstElementChild)
label.removeChild(label.firstElementChild);for(let i=0;i<items.length;i++){items[i].removeAttribute('display');if(items[i].hasAttribute('selected')){if(ndisplay-->0){items[i].setAttribute('display',n++);if(label)
label.appendChild(items[i].cloneNode(true));}
const c=items[i].querySelector('input[type="checkbox"]');if(c)
c.disabled=(sel==1&&!this.options.optional);}}
if(ndisplay<0)
sb.setAttribute('more','');else
sb.removeAttribute('more');if(ndisplay===this.options.display_items)
sb.setAttribute('empty','');else
sb.removeAttribute('empty');dom.content(more,(ndisplay===this.options.display_items)?(this.options.select_placeholder??this.options.placeholder):'···');}
else{let sel=li.parentNode.querySelector('[selected]');if(sel){sel.removeAttribute('display');sel.removeAttribute('selected');}
li.setAttribute('display',0);li.setAttribute('selected','');this.closeDropdown(sb);}
this.saveValues(sb,ul);},transformItem(sb,li){const cbox=E('form',{},E('input',{type:'checkbox',tabindex:-1,onclick:'event.preventDefault()'}));const label=E('label');while(li.firstChild)
label.appendChild(li.firstChild);li.appendChild(cbox);li.appendChild(label);},saveValues(sb,ul){const sel=ul.querySelectorAll('li[selected]');const div=sb.lastElementChild;const name=this.options.name;let strval='';const values=[];while(div.lastElementChild)
div.removeChild(div.lastElementChild);sel.forEach(s=>{if(s.hasAttribute('placeholder'))
return;const v={text:s.innerText,value:s.hasAttribute('data-value')?s.getAttribute('data-value'):s.innerText,element:s};div.appendChild(E('input',{type:'hidden',name:name,value:v.value}));values.push(v);strval+=strval.length?` ${v.value}`:v.value;});const detail={instance:this,element:sb};if(this.options.multiple)
detail.values=values;else
detail.value=values.length?values[0]:null;sb.value=strval;sb.dispatchEvent(new CustomEvent('cbi-dropdown-change',{bubbles:true,detail:detail}));},setValues(sb,values){const ul=sb.querySelector('ul');if(this.options.create){for(const value in values){this.createItems(sb,value);if(!this.options.multiple)
break;}}
if(this.options.multiple){const lis=ul.querySelectorAll('li[data-value]');for(let i=0;i<lis.length;i++){const value=lis[i].getAttribute('data-value');if(values===null||!(value in values))
this.toggleItem(sb,lis[i],false);else
this.toggleItem(sb,lis[i],true);}}
else{const ph=ul.querySelector('li[placeholder]');if(ph)
this.toggleItem(sb,ph);const lis=ul.querySelectorAll('li[data-value]');for(let i=0;i<lis.length;i++){const value=lis[i].getAttribute('data-value');if(values!==null&&(value in values))
this.toggleItem(sb,lis[i]);}}},setFocus(sb,elem,scroll){if(sb.hasAttribute('locked-in'))
return;sb.querySelectorAll('.focus').forEach(e=>{e.classList.remove('focus');});elem.classList.add('focus');if(scroll)
elem.parentNode.scrollTop=elem.offsetTop-elem.parentNode.offsetTop;elem.focus();},handleMouseout(ev){const sb=ev.currentTarget;if(!sb.hasAttribute('open'))
return;sb.querySelectorAll('.focus').forEach(e=>{e.classList.remove('focus');});sb.querySelector('ul.dropdown').focus();},createChoiceElement(sb,value,label){const tpl=sb.querySelector(this.options.create_template);let markup=null;if(tpl)
markup=(tpl.textContent??tpl.innerHTML??tpl.firstChild.data).replace(/^<!--|--!?>$/g,'').trim();else
markup='<li data-value="{{value}}"><span data-label-placeholder="true" /></li>';const new_item=E(markup.replace(/{{value}}/g,'%h'.format(value)));const placeholder=new_item.querySelector('[data-label-placeholder]');if(placeholder){const content=E('span',{},label??this.choices[value]??[value]);while(content.firstChild)
placeholder.parentNode.insertBefore(content.firstChild,placeholder);placeholder.parentNode.removeChild(placeholder);}
if(this.options.multiple)
this.transformItem(sb,new_item);if(!new_item.hasAttribute('unselectable'))
new_item.setAttribute('tabindex',0);return new_item;},createItems(sb,value){const sbox=this;let val=(value??'').trim();const ul=sb.querySelector('ul');if(!sbox.options.multiple)
val=val.length?[val]:[];else
val=val.length?val.split(/\s+/):[];val.forEach(item=>{let new_item=null;ul.childNodes.forEach(li=>{if(li.getAttribute&&li.getAttribute('data-value')===item)
new_item=li;});if(!new_item){new_item=sbox.createChoiceElement(sb,item);if(!sbox.options.multiple){const old=ul.querySelector('li[created]');if(old)
ul.removeChild(old);new_item.setAttribute('created','');}
new_item=ul.insertBefore(new_item,ul.lastElementChild);}
sbox.toggleItem(sb,new_item,true);sbox.setFocus(sb,new_item,true);});},clearChoices(reset_value){const ul=this.node.querySelector('ul');const lis=ul?ul.querySelectorAll('li[data-value]'):[];const len=lis.length-(this.options.create?1:0);const val=reset_value?null:this.getValue();for(let i=0;i<len;i++){const lival=lis[i].getAttribute('data-value');if(val==null||(!this.options.multiple&&val!=lival)||(this.options.multiple&&val.indexOf(lival)==-1))
ul.removeChild(lis[i]);}
if(reset_value)
this.setValues(this.node,{});},addChoices(values,labels){const sb=this.node;const ul=sb.querySelector('ul');const lis=ul?ul.querySelectorAll('li[data-value]'):[];if(!Array.isArray(values))
values=L.toArray(values);if(!L.isObject(labels))
labels={};for(let i=0;i<values.length;i++){let found=false;for(let j=0;j<lis.length;j++){if(lis[j].getAttribute('data-value')===values[i]){found=true;break;}}
if(found)
continue;ul.insertBefore(this.createChoiceElement(sb,values[i],labels[values[i]]),ul.lastElementChild);}},closeAllDropdowns(){document.querySelectorAll('.cbi-dropdown[open]').forEach(s=>{s.dispatchEvent(new CustomEvent('cbi-dropdown-close',{}));});},handleClick(ev){const sb=ev.currentTarget;if(!sb.hasAttribute('open')){if(!matchesElem(ev.target,'input'))
this.openDropdown(sb);}
else{const li=findParent(ev.target,'li');if(li&&li.parentNode.classList.contains('dropdown'))
this.toggleItem(sb,li);else if(li&&li.parentNode.classList.contains('preview'))
this.closeDropdown(sb);else if(matchesElem(ev.target,'span.open, span.more'))
this.closeDropdown(sb);}
ev.preventDefault();ev.stopPropagation();},handleKeydown(ev){const sb=ev.currentTarget;const ul=sb.querySelector('ul.dropdown');if(matchesElem(ev.target,'input'))
return;if(!sb.hasAttribute('open')){switch(ev.keyCode){case 37:case 38:case 39:case 40:this.openDropdown(sb);ev.preventDefault();}}
else{const active=findParent(document.activeElement,'li');switch(ev.keyCode){case 27:this.closeDropdown(sb);ev.stopPropagation();break;case 13:if(active){if(!active.hasAttribute('selected'))
this.toggleItem(sb,active);this.closeDropdown(sb);ev.preventDefault();}
break;case 32:if(active){this.toggleItem(sb,active);ev.preventDefault();}
break;case 38:if(active&&active.previousElementSibling){this.setFocus(sb,active.previousElementSibling);ev.preventDefault();}
else if(document.activeElement===ul){this.setFocus(sb,ul.lastElementChild);ev.preventDefault();}
break;case 40:if(active&&active.nextElementSibling){const li=active.nextElementSibling;this.setFocus(sb,li);if(this.options.create&&li==li.parentNode.lastElementChild){const input=li.querySelector('input:not([type="hidden"]):not([type="checkbox"]');if(input)input.focus();}
ev.preventDefault();}
else if(document.activeElement===ul){this.setFocus(sb,ul.firstElementChild);ev.preventDefault();}
break;}}},handleDropdownClose(ev){const sb=ev.currentTarget;this.closeDropdown(sb,true);},handleDropdownSelect(ev){const sb=ev.currentTarget;const li=findParent(ev.target,'li');if(!li)
return;this.toggleItem(sb,li);this.closeDropdown(sb,true);},handleMouseover(ev){const sb=ev.currentTarget;if(!sb.hasAttribute('open'))
return;const li=findParent(ev.target,'li');if(li&&li.parentNode.classList.contains('dropdown'))
this.setFocus(sb,li);},handleFocus(ev){const sb=ev.currentTarget;document.querySelectorAll('.cbi-dropdown[open]').forEach(s=>{if(s!==sb||sb.hasAttribute('open'))
s.dispatchEvent(new CustomEvent('cbi-dropdown-close',{}));});},handleCanaryFocus(ev){this.closeDropdown(ev.currentTarget.parentNode);},handleCreateKeydown(ev){const input=ev.currentTarget;const li=findParent(input,'li');const sb=findParent(li,'.cbi-dropdown');switch(ev.keyCode){case 13:ev.preventDefault();if(input.classList.contains('cbi-input-invalid'))
return;this.handleCreateBlur(ev);this.createItems(sb,input.value);input.value='';break;case 27:this.handleCreateBlur(ev);this.closeDropdown(sb);ev.stopPropagation();input.value='';break;case 38:if(li.previousElementSibling){this.handleCreateBlur(ev);this.setFocus(sb,li.previousElementSibling,true);}
break;}},handleCreateFocus(ev){const input=ev.currentTarget;const li=findParent(input,'li');const cbox=li.querySelector('input[type="checkbox"]');const sb=findParent(input,'.cbi-dropdown');if(cbox)
cbox.checked=true;sb.setAttribute('locked-in','');this.setFocus(sb,li,true);},handleCreateBlur(ev){const input=ev.currentTarget;const cbox=findParent(input,'li').querySelector('input[type="checkbox"]');const sb=findParent(input,'.cbi-dropdown');if(cbox)
cbox.checked=false;sb.removeAttribute('locked-in');},handleCreateClick(ev){ev.currentTarget.querySelector(this.options.create_query).focus();},setValue(values){if(this.options.multiple){if(!Array.isArray(values))
values=(values!=null&&values!='')?[values]:[];const v={};for(let i=0;i<values.length;i++)
v[values[i]]=true;this.setValues(this.node,v);}
else{const v={};if(values!=null){if(Array.isArray(values))
v[values[0]]=true;else
v[values]=true;}
this.setValues(this.node,v);}},getValue(){const div=this.node.lastElementChild;const h=div.querySelectorAll('input[type="hidden"]');const v=[];for(let i=0;i<h.length;i++)
v.push(h[i].value);return this.options.multiple?v:v[0];}});const UICombobox=UIDropdown.extend({__init__(value,choices,options){this.super('__init__',[value,choices,Object.assign({select_placeholder:_('-- Please choose --'),custom_placeholder:_('-- custom --'),dropdown_items:-1,sort:true},options,{multiple:false,create:true,optional:true})]);}});const UIComboButton=UIDropdown.extend({__init__(value,choices,options){this.super('__init__',[value,choices,Object.assign({sort:true},options,{multiple:false,create:false,optional:false})]);},render(...args){const node=UIDropdown.prototype.render.call(this,...args);const val=this.getValue();if(L.isObject(this.options.classes)&&this.options.classes.hasOwnProperty(val))
node.setAttribute('class',`cbi-dropdown ${this.options.classes[val]}`);return node;},handleClick(ev,...args){const sb=ev.currentTarget;const t=ev.target;if(sb.hasAttribute('open')||dom.matches(t,'.cbi-dropdown > span.open'))
return UIDropdown.prototype.handleClick.call(this,ev,...args);if(this.options.click)
return this.options.click.call(sb,ev,this.getValue());},toggleItem(sb,...args){const rv=UIDropdown.prototype.toggleItem.call(this,sb,...args);const val=this.getValue();if(L.isObject(this.options.classes)&&this.options.classes.hasOwnProperty(val))
sb.setAttribute('class',`cbi-dropdown ${this.options.classes[val]}`);else
sb.setAttribute('class','cbi-dropdown');return rv;}});const UIDynamicList=UIElement.extend({__init__(values,choices,options){if(!Array.isArray(values))
values=(values!=null&&values!='')?[values]:[];if(typeof(choices)!='object')
choices=null;this.values=values;this.choices=choices;this.options=Object.assign({},options,{multiple:false,optional:true});},render(){const dl=E('div',{'id':this.options.id,'class':'cbi-dynlist','disabled':this.options.disabled?'':null},E('div',{'class':'add-item control-group'}));if(this.choices){if(this.options.placeholder!=null)
this.options.select_placeholder=this.options.placeholder;const cbox=new UICombobox(null,this.choices,this.options);dl.lastElementChild.appendChild(cbox.render());}
else{const inputEl=E('input',{'id':this.options.id?`widget.${this.options.id}`:null,'type':'text','class':'cbi-input-text','placeholder':this.options.placeholder,'disabled':this.options.disabled?'':null});dl.lastElementChild.appendChild(inputEl);dl.lastElementChild.appendChild(E('div',{'class':'btn cbi-button cbi-button-add'},'+'));if(this.options.datatype||this.options.validate)
UI.prototype.addValidator(inputEl,this.options.datatype??'string',true,this.options.validate,'blur','keyup');}
for(let i=0;i<this.values.length;i++){let label=this.choices?this.choices[this.values[i]]:null;if(dom.elem(label))
label=label.cloneNode(true);this.addItem(dl,this.values[i],label);}
this.initDragAndDrop(dl);return this.bind(dl);},initDragAndDrop(dl){let draggedItem=null;let placeholder=null;dl.addEventListener('dragstart',(e)=>{if(e.target.classList.contains('item')){draggedItem=e.target;e.target.classList.add('dragging');}});dl.addEventListener('dragend',(e)=>e.target.classList.remove('dragging'));dl.addEventListener('dragover',(e)=>e.preventDefault());dl.addEventListener('dragenter',(e)=>e.target.classList.add('drag-over'));dl.addEventListener('dragleave',(e)=>e.target.classList.remove('drag-over'));dl.addEventListener('drop',(e)=>{e.preventDefault();e.target.classList.remove('drag-over');const target=e.target.classList.contains('item')?e.target:dl.querySelector('.add-item');dl.insertBefore(draggedItem,target);this.dispatchCbiDynlistChange(dl,draggedItem.value);});dl.addEventListener('click',(e)=>{if(e.target.closest('.item')){const span=e.target.closest('.item').querySelector('SPAN');if(span){const range=document.createRange();range.selectNodeContents(span);const selection=window.getSelection();if(selection.rangeCount===0||selection.toString().length===0){selection.removeAllRanges();selection.addRange(range);}else selection.removeAllRanges();}}});dl.addEventListener('touchstart',(e)=>{const touch=e.touches[0];const target=e.target.closest('.item');if(target){draggedItem=target;placeholder=draggedItem.cloneNode(true);placeholder.className='placeholder';placeholder.style.height=`${draggedItem.offsetHeight}px`;draggedItem.parentNode.insertBefore(placeholder,draggedItem.nextSibling);draggedItem.classList.add('dragging')}});dl.addEventListener('touchmove',(e)=>{if(draggedItem){const touch=e.touches[0];const currentY=touch.clientY;const items=Array.from(dl.querySelectorAll('.item'));const target=items.find(item=>{const rect=item.getBoundingClientRect();return currentY>rect.top&&currentY<rect.bottom;});if(target&&target!==draggedItem){const insertBefore=currentY<target.getBoundingClientRect().top+target.offsetHeight/2;dl.insertBefore(placeholder,insertBefore?target:target.nextSibling);}
e.preventDefault();}});dl.addEventListener('touchend',(e)=>{if(draggedItem&&placeholder){dl.insertBefore(draggedItem,placeholder);draggedItem.classList.remove('dragging')
placeholder.parentNode.removeChild(placeholder);this.dispatchCbiDynlistChange(dl,draggedItem.value);placeholder=null;draggedItem=null;}});},bind(dl){dl.addEventListener('click',L.bind(this.handleClick,this));dl.addEventListener('keydown',L.bind(this.handleKeydown,this));dl.addEventListener('cbi-dropdown-change',L.bind(this.handleDropdownChange,this));this.node=dl;this.setUpdateEvents(dl,'cbi-dynlist-change');this.setChangeEvents(dl,'cbi-dynlist-change');dom.bindClassInstance(dl,this);return dl;},addItem(dl,value,text,flash){let exists=false;const new_item=E('div',{'class':flash?'item flash':'item','tabindex':0,'draggable':true},[E('span',{},[text??value]),E('input',{'type':'hidden','name':this.options.name,'value':value})]);dl.querySelectorAll('.item').forEach(item=>{if(exists)
return;let hidden=item.querySelector('input[type="hidden"]');if(hidden&&hidden.parentNode!==item)
hidden=null;if(hidden&&hidden.value===value)
exists=true;});if(!exists){const ai=dl.querySelector('.add-item');ai.parentNode.insertBefore(new_item,ai);}
this.dispatchCbiDynlistChange(dl,value);},dispatchCbiDynlistChange(dl,value){dl.dispatchEvent(new CustomEvent('cbi-dynlist-change',{bubbles:true,detail:{instance:this,element:dl,value:value,add:true}}));},removeItem(dl,item){const value=item.querySelector('input[type="hidden"]').value;const sb=dl.querySelector('.cbi-dropdown');if(sb)
sb.querySelectorAll('ul > li').forEach(li=>{if(li.getAttribute('data-value')===value){if(li.hasAttribute('dynlistcustom'))
li.parentNode.removeChild(li);else
li.removeAttribute('unselectable');}});item.parentNode.removeChild(item);this.dispatchCbiDynlistChange(dl,value);},handleClick(ev){const dl=ev.currentTarget;const item=findParent(ev.target,'.item');if(this.options.disabled)
return;if(item){const rect=item.getBoundingClientRect();const afterStyles=window.getComputedStyle(item,'::after');const afterWidth=parseFloat(afterStyles.width)||0;if(rect.right-ev.clientX<=afterWidth){this.removeItem(dl,item);}}
else if(matchesElem(ev.target,'.cbi-button-add')){const input=ev.target.previousElementSibling;if(input.value.length&&!input.classList.contains('cbi-input-invalid')){this.addItem(dl,input.value,null,true);input.value='';}}},handleDropdownChange(ev){const dl=ev.currentTarget;const sbIn=ev.detail.instance;const sbEl=ev.detail.element;const sbVal=ev.detail.value;if(sbVal===null)
return;sbIn.setValues(sbEl,null);sbVal.element.setAttribute('unselectable','');if(sbVal.element.hasAttribute('created')){sbVal.element.removeAttribute('created');sbVal.element.setAttribute('dynlistcustom','');}
let label=sbVal.text;if(sbVal.element){label=E([]);for(let i=0;i<sbVal.element.childNodes.length;i++)
label.appendChild(sbVal.element.childNodes[i].cloneNode(true));}
this.addItem(dl,sbVal.value,label,true);},handleKeydown(ev){const dl=ev.currentTarget;const item=findParent(ev.target,'.item');if(item){switch(ev.keyCode){case 8:if(item.previousElementSibling)
item.previousElementSibling.focus();this.removeItem(dl,item);break;case 46:if(item.nextElementSibling){if(item.nextElementSibling.classList.contains('item'))
item.nextElementSibling.focus();else
item.nextElementSibling.firstElementChild.focus();}
this.removeItem(dl,item);break;}}
else if(matchesElem(ev.target,'.cbi-input-text')){switch(ev.keyCode){case 13:if(ev.target.value.length&&!ev.target.classList.contains('cbi-input-invalid')){this.addItem(dl,ev.target.value,null,true);ev.target.value='';ev.target.blur();ev.target.focus();}
ev.preventDefault();break;}}},getValue(){const items=this.node.querySelectorAll('.item > input[type="hidden"]');const input=this.node.querySelector('.add-item > input[type="text"]');const v=[];for(let i=0;i<items.length;i++)
v.push(items[i].value);if(input&&input.value!=null&&input.value.match(/\S/)&&input.classList.contains('cbi-input-invalid')==false&&v.filter(s=>s==input.value).length==0)
v.push(input.value);return v;},setValue(values){if(!Array.isArray(values))
values=(values!=null&&values!='')?[values]:[];const items=this.node.querySelectorAll('.item');for(let i=0;i<items.length;i++)
if(items[i].parentNode===this.node)
this.removeItem(this.node,items[i]);for(let i=0;i<values.length;i++)
this.addItem(this.node,values[i],this.choices?this.choices[values[i]]:null);},addChoices(values,labels){const dl=this.node.lastElementChild.firstElementChild;dom.callClassMethod(dl,'addChoices',values,labels);},clearChoices(){const dl=this.node.lastElementChild.firstElementChild;dom.callClassMethod(dl,'clearChoices');}});const UIRangeSlider=UIElement.extend({__init__(value,options){this.value=value;this.options=Object.assign({optional:true,min:0,max:100,step:1,calculate:null,calcunits:null,usecalc:false,disabled:false,},options);},render(){this.sliderEl=E('input',{'type':'range','id':this.options.id,'min':this.options.min,'max':this.options.max,'step':this.options.step||'any','value':this.value,'disabled':this.options.disabled?'':null});this.calculatedvalue=(typeof this.options.calculate==='function')?this.options.calculate(this.value):null;this.calcEl=E('output',{'class':'cbi-range-slider-calc'},this.calculatedvalue);this.calcunitsEl=E('span',{'class':'cbi-range-slider-calc-units'},this.options.calcunits?'&nbsp;'+this.options.calcunits:'');const container=E('div',{'class':'cbi-range-slider'},[this.sliderEl,this.valueEl=E('output',{'for':this.options.id,'class':'cbi-range-slider-value'},this.value),this.calculatedvalue?E('br'):null,this.calculatedvalue?this.calcEl:null,this.calculatedvalue?this.calcunitsEl:null,].filter(Boolean));this.node=container;this.setUpdateEvents(this.sliderEl,'input','blur');this.setChangeEvents(this.sliderEl,'change');this.sliderEl.addEventListener('input',()=>{const val=this.sliderEl.value;this.valueEl.textContent=val;if(typeof this.options.calculate==='function'){this.calculatedvalue=this.options.calculate(val);this.calcEl.textContent=this.calculatedvalue;}
this.node.setAttribute('data-changed',true);});dom.bindClassInstance(container,this);return container;},getValue(){return this.sliderEl.value;},getCalculatedValue(){return this.calculatedvalue;},setValue(value){this.sliderEl.value=value;this.valueEl.textContent=value;if(typeof this.options.calculate==='function'){this.calculatedvalue=this.options.calculate(value);this.calcEl.textContent=this.calculatedvalue;}}});const UIHiddenfield=UIElement.extend({__init__(value,options){this.value=value;this.options=Object.assign({},options);},render(){const hiddenEl=E('input',{'id':this.options.id,'type':'hidden','value':this.value});return this.bind(hiddenEl);},bind(hiddenEl){this.node=hiddenEl;dom.bindClassInstance(hiddenEl,this);return hiddenEl;},getValue(){return this.node.value;},setValue(value){this.node.value=value;}});const UIFileUpload=UIElement.extend({__init__(value,options){this.value=value;this.options=Object.assign({browser:false,show_hidden:false,enable_upload:true,enable_remove:true,enable_download:false,root_directory:'/etc/luci-uploads'},options);},bind(browserEl){this.node=browserEl;this.setUpdateEvents(browserEl,'cbi-fileupload-select','cbi-fileupload-cancel');this.setChangeEvents(browserEl,'cbi-fileupload-select','cbi-fileupload-cancel');dom.bindClassInstance(browserEl,this);return browserEl;},render(){const renderFileBrowser=L.resolveDefault(this.value!=null?fs.stat(this.value):null).then(L.bind((stat)=>{let label;if(L.isObject(stat)&&stat.type!='directory')
this.stat=stat;if(this.stat!=null)
label=[this.iconForType(this.stat.type),' %s (%1000mB)'.format(this.truncatePath(this.stat.path),this.stat.size)];else if(this.value!=null)
label=[this.iconForType('file'),' %s (%s)'.format(this.truncatePath(this.value),_('File not accessible'))];else
label=[_('Select file…')];let btnOpenFileBrowser=E('button',{'class':'btn open-file-browser','click':UI.prototype.createHandlerFn(this,'handleFileBrowser'),'disabled':this.options.disabled?'':null},label);const fileBrowserEl=E('div',{'id':this.options.id},[btnOpenFileBrowser,E('div',{'class':'cbi-filebrowser'}),E('input',{'type':'hidden','name':this.options.name,'value':this.value})]);return this.bind(fileBrowserEl);},this));if(this.options.browser){return renderFileBrowser.then((fileBrowserEl)=>{const btnOpenFileBrowser=fileBrowserEl.getElementsByClassName('open-file-browser').item(0);btnOpenFileBrowser.click();return fileBrowserEl;});}
return renderFileBrowser},truncatePath(path){if(path.length>50)
path=`${path.substring(0, 25)}…${path.substring(path.length - 25)}`;return path;},iconForType(type){switch(type){case'symlink':return E('img',{'src':L.resource('cbi/link.svg'),'width':16,'title':_('Symbolic link'),'class':'middle'});case'directory':return E('img',{'src':L.resource('cbi/folder.svg'),'width':16,'title':_('Directory'),'class':'middle'});default:return E('img',{'src':L.resource('cbi/file.svg'),'width':16,'title':_('File'),'class':'middle'});}},canonicalizePath(path){return path.replace(/\/{2,}/g,'/').replace(/\/\.(\/|$)/g,'/').replace(/[^\/]+\/\.\.(\/|$)/g,'/').replace(/\/$/g,(m,o,s)=>s.length>1?'':'/');},splitPath(path){const croot=this.canonicalizePath(this.options.root_directory??'/');const cpath=this.canonicalizePath(path??'/');if(cpath.length<=croot.length)
return[croot];const parts=cpath.substring(croot.length).split(/\//);parts.unshift(croot);return parts;},handleUpload(path,list,ev){const form=ev.target.parentNode;const fileinput=form.querySelector('input[type="file"]');const nameinput=form.querySelector('input[type="text"]');const filename=(nameinput.value!=null?nameinput.value:'').trim();ev.preventDefault();if(filename==''||filename.match(/\//)||fileinput.files[0]==null)
return;const existing=list.filter(e=>e.name==filename)[0];if(existing!=null&&existing.type=='directory')
return alert(_('A directory with the same name already exists.'));else if(existing!=null&&!confirm(_('Overwrite existing file "%s" ?').format(filename)))
return;const data=new FormData();data.append('sessionid',L.env.sessionid);data.append('filename',`${path}/${filename}`);data.append('filedata',fileinput.files[0]);return request.post(`${L.env.cgi_base}/cgi-upload`,data,{progress:L.bind((btn,ev)=>{btn.firstChild.data='%.2f%%'.format((ev.loaded/ev.total)*100);},this,ev.target)}).then(L.bind((path,ev,res)=>{const reply=res.json();if(L.isObject(reply)&&reply.failure)
alert(_('Upload request failed: %s').format(reply.message));return this.handleSelect(path,null,ev);},this,path,ev));},handleDelete(path,fileStat,ev){const parent=path.replace(/\/[^\/]+$/,'')??'/';const name=path.replace(/^.+\//,'');let msg;ev.preventDefault();if(fileStat.type=='directory')
msg=_('Do you really want to delete the "%s" directory recursively?').format(name);else
msg=_('Do you really want to delete "%s" ?').format(name);if(confirm(msg)){const button=this.node.firstElementChild;const hidden=this.node.lastElementChild;if(path==hidden.value){dom.content(button,_('Select file…'));hidden.value='';}
return fs.remove(path).then(L.bind((parent,ev)=>{return this.handleSelect(parent,null,ev);},this,parent,ev)).catch(err=>{alert(_('Delete request failed: %s').format(err.message));});}},renderUpload(path,list){if(!this.options.enable_upload)
return E([]);return E([E('a',{'href':'#','class':'btn cbi-button-positive','click':function(ev){const uploadForm=ev.target.nextElementSibling;const fileInput=uploadForm.querySelector('input[type="file"]');ev.target.style.display='none';uploadForm.style.display='';fileInput.click();}},_('Upload file…')),E('div',{'class':'upload','style':'display:none'},[E('input',{'type':'file','style':'display:none','change':function(ev){const nameinput=ev.target.parentNode.querySelector('input[type="text"]');const uploadbtn=ev.target.parentNode.querySelector('button.cbi-button-save');nameinput.value=ev.target.value.replace(/^.+[\/\\]/,'');uploadbtn.disabled=false;}}),E('button',{'class':'btn','click':function(ev){ev.preventDefault();ev.target.previousElementSibling.click();}},[_('Browse…')]),E('div',{},E('input',{'type':'text','placeholder':_('Filename')})),E('button',{'class':'btn cbi-button-save','click':UI.prototype.createHandlerFn(this,'handleUpload',path,list),'disabled':true},[_('Upload file')])])]);},renderListing(container,path,list){const breadcrumb=E('p');const rows=E('ul');list.sort((a,b)=>{return L.naturalCompare(a.type=='directory',b.type=='directory')||L.naturalCompare(a.name,b.name);});for(let i=0;i<list.length;i++){if(!this.options.show_hidden&&list[i].name.charAt(0)=='.')
continue;const entrypath=this.canonicalizePath(`${path}/${list[i].name}`);const selected=(entrypath==this.node.lastElementChild.value);const mtime=new Date(list[i].mtime*1000);rows.appendChild(E('li',[E('div',{'class':'name'},[this.iconForType(list[i].type),' ',E('a',{'href':'#','style':selected?'font-weight:bold':null,'click':UI.prototype.createHandlerFn(this,'handleSelect',entrypath,list[i].type!='directory'?list[i]:null)},'%h'.format(list[i].name))]),E('div',{'class':'mtime hide-xs'},[' %04d-%02d-%02d %02d:%02d:%02d '.format(mtime.getFullYear(),mtime.getMonth()+1,mtime.getDate(),mtime.getHours(),mtime.getMinutes(),mtime.getSeconds())]),E('div',[selected?E('button',{'class':'btn','click':UI.prototype.createHandlerFn(this,'handleReset')},[_('Deselect')]):'',this.options.enable_download&&list[i].type=='file'?E('button',{'class':'btn','click':UI.prototype.createHandlerFn(this,'handleDownload',entrypath,list[i])},[_('Download')]):'',this.options.enable_remove?E('button',{'class':'btn cbi-button-negative','click':UI.prototype.createHandlerFn(this,'handleDelete',entrypath,list[i])},[_('Delete')]):''])]));}
if(!rows.firstElementChild)
rows.appendChild(E('em',_('No entries in this directory')));const dirs=this.splitPath(path);let cur='';for(let i=0;i<dirs.length;i++){cur+=dirs[i];dom.append(breadcrumb,[i?' » ':'',E('a',{'href':'#','click':UI.prototype.createHandlerFn(this,'handleSelect',cur??'/',null)},dirs[i]!=='/'?'%h'.format(dirs[i]):E('em','(root)')),]);}
dom.content(container,[breadcrumb,rows,E('div',{'class':'right'},[this.renderUpload(path,list),!this.options.browser?E('a',{'href':'#','class':'btn','click':UI.prototype.createHandlerFn(this,'handleCancel')},_('Cancel')):'']),]);},handleCancel(ev){const button=this.node.firstElementChild;const browser=button.nextElementSibling;browser.classList.remove('open');button.style.display='';this.node.dispatchEvent(new CustomEvent('cbi-fileupload-cancel',{}));ev.preventDefault();},handleReset(ev){const button=this.node.firstElementChild;const hidden=this.node.lastElementChild;hidden.value='';dom.content(button,_('Select file…'));this.handleCancel(ev);},handleDownload(path,fileStat,ev){fs.read_direct(path,'blob').then((blob)=>{const url=window.URL.createObjectURL(blob);let a=document.createElement('a');a.style.display='none';a.href=url;a.download=fileStat.name;document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);}).catch((err)=>{alert(_('Download failed: %s').format(err.message));});},handleSelect(path,fileStat,ev){const browser=dom.parent(ev.target,'.cbi-filebrowser');const ul=browser.querySelector('ul');if(fileStat==null){dom.content(ul,E('em',{'class':'spinning'},_('Loading directory contents…')));L.resolveDefault(fs.list(path),[]).then(L.bind(this.renderListing,this,browser,path));}
else if(!this.options.browser){const button=this.node.firstElementChild;const hidden=this.node.lastElementChild;path=this.canonicalizePath(path);dom.content(button,[this.iconForType(fileStat.type),' %s (%1000mB)'.format(this.truncatePath(path),fileStat.size)]);browser.classList.remove('open');button.style.display='';hidden.value=path;this.stat=Object.assign({path:path},fileStat);this.node.dispatchEvent(new CustomEvent('cbi-fileupload-select',{detail:this.stat}));}},handleFileBrowser(ev){const button=ev.target;const browser=button.nextElementSibling;let path=this.stat?this.stat.path.replace(/\/[^\/]+$/,''):(this.options.initial_directory??this.options.root_directory);if(path.indexOf(this.options.root_directory)!=0)
path=this.options.root_directory;ev.preventDefault();return L.resolveDefault(fs.list(path),[]).then(L.bind((button,browser,path,list)=>{document.querySelectorAll('.cbi-filebrowser.open').forEach(browserEl=>{dom.findClassInstance(browserEl).handleCancel(ev);});button.style.display='none';browser.classList.add('open');return this.renderListing(browser,path,list);},this,button,browser,path));},getValue(){return this.node.lastElementChild.value;},setValue(value){this.node.lastElementChild.value=value;}});function scrubMenu(node){let hasSatisfiedChild=false;if(L.isObject(node.children)){for(const k in node.children){const child=scrubMenu(node.children[k]);if(child.title&&!child.firstchild_ineligible)
hasSatisfiedChild||=child.satisfied;}}
if(L.isObject(node.action)&&node.action.type=='firstchild'&&hasSatisfiedChild==false)
node.satisfied=false;return node;}
const UIMenu=baseclass.singleton({load(){if(this.menu==null)
this.menu=session.getLocalData('menu');if(!L.isObject(this.menu)){this.menu=request.get(L.url('admin/menu')).then(L.bind((menu)=>{this.menu=scrubMenu(menu.json());session.setLocalData('menu',this.menu);return this.menu;},this));}
return Promise.resolve(this.menu);},flushCache(){session.setLocalData('menu',null);},getChildren(node){const children=[];if(node==null)
node=this.menu;for(const k in node.children){if(!node.children.hasOwnProperty(k))
continue;if(!node.children[k].satisfied)
continue;if(!node.children[k].hasOwnProperty('title'))
continue;let subnode=Object.assign(node.children[k],{name:k});if(L.isObject(subnode.action)&&subnode.action.path!=null&&(subnode.action.type=='alias'||subnode.action.type=='rewrite')){let root=this.menu;const path=subnode.action.path.split('/');for(let i=0;root!=null&&i<path.length;i++)
root=L.isObject(root.children)?root.children[path[i]]:null;if(root)
subnode=Object.assign({},subnode,{children:root.children,action:root.action});}
children.push(subnode);}
return children.sort((a,b)=>{const wA=a.order??1000;const wB=b.order??1000;if(wA!=wB)
return wA-wB;return L.naturalCompare(a.name,b.name);});}});const UITable=baseclass.extend({__init__(captions,options,placeholder){if(!Array.isArray(captions)){this.initFromMarkup(captions);return;}
const id=options.id??'table%08x'.format(Math.random()*0xffffffff);const table=E('table',{'id':id,'class':'table'},[E('tr',{'class':'tr table-titles','click':UI.prototype.createHandlerFn(this,'handleSort')})]);this.id=id;this.node=table
this.options=options;const sorting=this.getActiveSortState();for(let i=0;i<captions.length;i++){if(captions[i]==null)
continue;const th=E('th',{'class':'th'},[captions[i]]);if(typeof(options.captionClasses)=='object')
DOMTokenList.prototype.add.apply(th.classList,L.toArray(options.captionClasses[i]));if(options.sortable!==false&&(typeof(options.sortable)!='object'||options.sortable[i]!==false)){th.setAttribute('data-sortable-row',true);if(sorting&&sorting[0]==i)
th.setAttribute('data-sort-direction',sorting[1]?'desc':'asc');}
table.firstElementChild.appendChild(th);}
if(placeholder){const trow=table.appendChild(E('tr',{'class':'tr placeholder'}));const td=trow.appendChild(E('td',{'class':'td'},placeholder));if(typeof(captionClasses)=='object')
DOMTokenList.prototype.add.apply(td.classList,L.toArray(captionClasses[0]));}
DOMTokenList.prototype.add.apply(table.classList,L.toArray(options.classes));},update(data,placeholderText){const placeholder=placeholderText??this.options.placeholder??_('No data','empty table placeholder');const sorting=this.getActiveSortState();if(!Array.isArray(data))
return;const headings=[].slice.call(this.node.firstElementChild.querySelectorAll('th, .th'));if(sorting){const list=data.map(L.bind((row)=>{return[this.deriveSortKey(row[sorting[0]],sorting[0]),row];},this));list.sort((a,b)=>{return sorting[1]?-L.naturalCompare(a[0],b[0]):L.naturalCompare(a[0],b[0]);});data.length=0;list.forEach(item=>{data.push(item[1]);});headings.forEach((th,i)=>{if(i==sorting[0])
th.setAttribute('data-sort-direction',sorting[1]?'desc':'asc');else
th.removeAttribute('data-sort-direction');});}
this.data=data;this.placeholder=placeholder;let n=0;const rows=this.node.querySelectorAll('tr, .tr');const trows=[];const captionClasses=this.options.captionClasses;const trTag=(rows[0]&&rows[0].nodeName=='DIV')?'div':'tr';const tdTag=(headings[0]&&headings[0].nodeName=='DIV')?'div':'td';data.forEach(row=>{trows[n]=E(trTag,{'class':'tr'});for(let i=0;i<headings.length;i++){const text=(headings[i].innerText??'').trim();const raw_val=Array.isArray(row[i])?row[i][0]:null;const disp_val=Array.isArray(row[i])?row[i][1]:row[i];const td=trows[n].appendChild(E(tdTag,{'class':'td','data-title':(text!=='')?text:null,'data-value':raw_val},(disp_val!=null)?((disp_val instanceof DocumentFragment)?disp_val.cloneNode(true):disp_val):''));if(typeof(captionClasses)=='object')
DOMTokenList.prototype.add.apply(td.classList,L.toArray(captionClasses[i]));if(!td.classList.contains('cbi-section-actions'))
headings[i].setAttribute('data-sortable-row',true);}
trows[n].classList.add('cbi-rowstyle-%d'.format((n++%2)?2:1));});for(let i=0;i<n;i++){if(rows[i+1])
this.node.replaceChild(trows[i],rows[i+1]);else
this.node.appendChild(trows[i]);}
while(rows[++n])
this.node.removeChild(rows[n]);if(placeholder&&this.node.firstElementChild===this.node.lastElementChild){const trow=this.node.appendChild(E(trTag,{'class':'tr placeholder'}));const td=trow.appendChild(E(tdTag,{'class':'td'},placeholder));if(typeof(captionClasses)=='object')
DOMTokenList.prototype.add.apply(td.classList,L.toArray(captionClasses[0]));}
return this.node;},render(){return this.node;},initFromMarkup(node){if(!dom.elem(node))
node=document.querySelector(node);if(!node)
throw'Invalid table selector';const options={};const headrow=node.querySelector('tr, .tr');if(!headrow)
return;options.id=node.id;options.classes=[].slice.call(node.classList).filter(c=>c!='table');options.sortable=[];options.captionClasses=[];headrow.querySelectorAll('th, .th').forEach((th,i)=>{options.sortable[i]=!th.classList.contains('cbi-section-actions');options.captionClasses[i]=[].slice.call(th.classList).filter(c=>c!='th');});headrow.addEventListener('click',UI.prototype.createHandlerFn(this,'handleSort'));this.id=node.id;this.node=node;this.options=options;},deriveSortKey(value,index){const opts=this.options??{};let hint;let m;if(opts.sortable==true||opts.sortable==null)
hint='auto';else if(typeof(opts.sortable)=='object')
hint=opts.sortable[index];if(dom.elem(value)){if(value.hasAttribute('data-value'))
value=value.getAttribute('data-value');else
value=(value.innerText??'').trim();}
switch(hint??'auto'){case true:case'auto':m=/^([0-9a-fA-F:.]+)(?:\/([0-9a-fA-F:.]+))?$/.exec(value);if(m){let addr;let mask;addr=validation.parseIPv6(m[1]);mask=m[2]?validation.parseIPv6(m[2]):null;if(addr&&mask!=null)
return'%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x%04x'.format(addr[0],addr[1],addr[2],addr[3],addr[4],addr[5],addr[6],addr[7],mask[0],mask[1],mask[2],mask[3],mask[4],mask[5],mask[6],mask[7]);else if(addr)
return'%04x%04x%04x%04x%04x%04x%04x%04x%02x'.format(addr[0],addr[1],addr[2],addr[3],addr[4],addr[5],addr[6],addr[7],m[2]?+m[2]:128);addr=validation.parseIPv4(m[1]);mask=m[2]?validation.parseIPv4(m[2]):null;if(addr&&mask!=null)
return'%03d%03d%03d%03d%03d%03d%03d%03d'.format(addr[0],addr[1],addr[2],addr[3],mask[0],mask[1],mask[2],mask[3]);else if(addr)
return'%03d%03d%03d%03d%02d'.format(addr[0],addr[1],addr[2],addr[3],m[2]?+m[2]:32);}
m=/^(?:(\d+)d )?(\d+)h (\d+)m (\d+)s$/.exec(value);if(m)
return'%05d%02d%02d%02d'.format(+m[1],+m[2],+m[3],+m[4]);m=/^(\d+)\b(\D*)$/.exec(value);if(m)
return'%010d%s'.format(+m[1],m[2]);return String(value);case'ignorecase':return String(value).toLowerCase();case'numeric':return+value;default:return String(value);}},getActiveSortState(){if(this.sortState)
return this.sortState;if(!this.options.id)
return null;const page=document.body.getAttribute('data-page');const key=`${page}.${this.id}`;const state=session.getLocalData('tablesort');if(L.isObject(state)&&Array.isArray(state[key]))
return state[key];return null;},setActiveSortState(index,descending){this.sortState=[index,descending];if(!this.options.id)
return;const page=document.body.getAttribute('data-page');const key=`${page}.${this.options.id}`;let state=session.getLocalData('tablesort');if(!L.isObject(state))
state={};state[key]=this.sortState;session.setLocalData('tablesort',state);},handleSort(ev){if(!ev.target.matches('th[data-sortable-row]'))
return;const th=ev.target;const direction=(th.getAttribute('data-sort-direction')=='asc');let index=0;this.node.firstElementChild.querySelectorAll('th').forEach((other_th,i)=>{if(other_th!==th)
other_th.removeAttribute('data-sort-direction');else
index=i;});this.setActiveSortState(index,direction);this.update(this.data,this.placeholder);}});const UI=baseclass.extend({__init__(){modalDiv=document.body.appendChild(dom.create('div',{id:'modal_overlay',tabindex:-1,keydown:this.cancelModal},[dom.create('div',{class:'modal',role:'dialog','aria-modal':true})]));tooltipDiv=document.body.appendChild(dom.create('div',{class:'cbi-tooltip'}));L.showModal=this.showModal;L.hideModal=this.hideModal;L.showTooltip=this.showTooltip;L.hideTooltip=this.hideTooltip;L.itemlist=this.itemlist;document.addEventListener('mouseover',this.showTooltip.bind(this),true);document.addEventListener('mouseout',this.hideTooltip.bind(this),true);document.addEventListener('focus',this.showTooltip.bind(this),true);document.addEventListener('blur',this.hideTooltip.bind(this),true);document.addEventListener('luci-loaded',this.tabs.init.bind(this.tabs));document.addEventListener('luci-loaded',this.changes.init.bind(this.changes));document.addEventListener('uci-loaded',this.changes.init.bind(this.changes));},showModal(title,children,...classes){const dlg=modalDiv.firstElementChild;dlg.setAttribute('class','modal');dlg.classList.add(...classes);dom.content(dlg,dom.create('h4',{},title));dom.append(dlg,children);document.body.classList.add('modal-overlay-active');modalDiv.scrollTop=0;modalDiv.focus();return dlg;},hideModal(){document.body.classList.remove('modal-overlay-active');modalDiv.blur();},cancelModal(ev){if(ev.key=='Escape'){const btn=modalDiv.querySelector('.right > button, .right > .btn, .button-row > .btn');if(btn)
btn.click();}},showTooltip(ev){const target=findParent(ev.target,'[data-tooltip]');if(!target)
return;if(tooltipTimeout!==null){window.clearTimeout(tooltipTimeout);tooltipTimeout=null;}
const rect=target.getBoundingClientRect();const x=rect.left+window.pageXOffset;let y=rect.top+rect.height+window.pageYOffset;let above=false;tooltipDiv.className='cbi-tooltip';tooltipDiv.innerHTML='▲ ';tooltipDiv.firstChild.data+=target.getAttribute('data-tooltip');if(target.hasAttribute('data-tooltip-style'))
tooltipDiv.classList.add(target.getAttribute('data-tooltip-style'));if((y+tooltipDiv.offsetHeight)>(window.innerHeight+window.pageYOffset))
above=true;const dropdown=target.querySelector('ul.dropdown[style]:first-child');if(dropdown&&dropdown.style.top)
above=true;if(above){y-=(tooltipDiv.offsetHeight+target.offsetHeight);tooltipDiv.firstChild.data=`▼ ${tooltipDiv.firstChild.data.substr(2)}`;}
tooltipDiv.style.top=`${y}px`;tooltipDiv.style.left=`${x}px`;tooltipDiv.style.opacity=1;tooltipDiv.dispatchEvent(new CustomEvent('tooltip-open',{bubbles:true,detail:{target:target}}));},hideTooltip(ev){if(ev.target===tooltipDiv||ev.relatedTarget===tooltipDiv||tooltipDiv.contains(ev.target)||tooltipDiv.contains(ev.relatedTarget))
return;if(tooltipTimeout!==null){window.clearTimeout(tooltipTimeout);tooltipTimeout=null;}
tooltipDiv.style.opacity=0;tooltipTimeout=window.setTimeout(()=>tooltipDiv.removeAttribute('style'),250);tooltipDiv.dispatchEvent(new CustomEvent('tooltip-close',{bubbles:true}));},addNotification(title,children,...classes){const mc=document.querySelector('#maincontent')??document.body;const msg=E('div',{'class':'alert-message fade-in','style':'display:flex','transitionend':function(ev){const node=ev.currentTarget;if(node.parentNode&&node.classList.contains('fade-out'))
node.parentNode.removeChild(node);}},[E('div',{'style':'flex:10'}),E('div',{'style':'flex:1 1 auto; display:flex'},[E('button',{'class':'btn','style':'margin-left:auto; margin-top:auto','click':function(ev){dom.parent(ev.target,'.alert-message').classList.add('fade-out');},},[_('Dismiss')])])]);if(title!=null)
dom.append(msg.firstElementChild,E('h4',{},title));dom.append(msg.firstElementChild,children);msg.classList.add(...classes);mc.insertBefore(msg,mc.firstElementChild);return msg;},addTimeLimitedNotification(title,children,timeout,...classes){const msg=this.addNotification(title,children,...classes);function fadeOutNotification(element){if(element){element.classList.add('fade-out');element.classList.remove('fade-in');setTimeout(()=>{if(element.parentNode){element.parentNode.removeChild(element);}});}}
if(typeof timeout==='number'&&timeout>0){setTimeout(()=>fadeOutNotification(msg),timeout);}
return msg;},showIndicator(id,label,handler,style){if(indicatorDiv==null){indicatorDiv=document.body.querySelector('#indicators');if(indicatorDiv==null)
return false;}
const handlerFn=(typeof(handler)=='function')?handler:null;let indicatorElem=indicatorDiv.querySelector('span[data-indicator="%s"]'.format(id));if(indicatorElem==null){let beforeElem=null;for(beforeElem=indicatorDiv.firstElementChild;beforeElem!=null;beforeElem=beforeElem.nextElementSibling)
if(beforeElem.getAttribute('data-indicator')>id)
break;indicatorElem=indicatorDiv.insertBefore(E('span',{'data-indicator':id,'data-clickable':handlerFn?true:null,'click':handlerFn},['']),beforeElem);}
if(label==indicatorElem.firstChild.data&&style==indicatorElem.getAttribute('data-style'))
return false;indicatorElem.firstChild.data=label;indicatorElem.setAttribute('data-style',(style=='inactive')?'inactive':'active');return true;},hideIndicator(id){const indicatorElem=indicatorDiv?indicatorDiv.querySelector('span[data-indicator="%s"]'.format(id)):null;if(indicatorElem==null)
return false;indicatorDiv.removeChild(indicatorElem);return true;},itemlist(node,items,separators){const children=[];if(!Array.isArray(separators))
separators=[separators??E('br')];for(let i=0;i<items.length;i+=2){if(items[i+1]!==null&&items[i+1]!==undefined){const sep=separators[(i/2)%separators.length];const cld=[];children.push(E('span',{class:'nowrap'},[items[i]?E('strong',`${items[i]}: `):'',items[i+1]]));if((i+2)<items.length)
children.push(dom.elem(sep)?sep.cloneNode(true):sep);}}
dom.content(node,children);return node;},tabs:baseclass.singleton({init(){const groups=[];let prevGroup=null;let currGroup=null;document.querySelectorAll('[data-tab]').forEach(tab=>{const parent=tab.parentNode;if(dom.matches(tab,'li')&&dom.matches(parent,'ul.cbi-tabmenu'))
return;if(!parent.hasAttribute('data-tab-group'))
parent.setAttribute('data-tab-group',groups.length);currGroup=+parent.getAttribute('data-tab-group');if(currGroup!==prevGroup){prevGroup=currGroup;if(!groups[currGroup])
groups[currGroup]=[];}
groups[currGroup].push(tab);});for(let i=0;i<groups.length;i++)
this.initTabGroup(groups[i]);document.addEventListener('dependency-update',this.updateTabs.bind(this));this.updateTabs();},initTabGroup(panes){if(typeof(panes)!='object'||!('length'in panes)||panes.length===0)
return;const menu=E('ul',{'class':'cbi-tabmenu'});const group=panes[0].parentNode;const groupId=+group.getAttribute('data-tab-group');let selected=null;if(group.getAttribute('data-initialized')==='true')
return;for(let i=0,pane;pane=panes[i];i++){const name=pane.getAttribute('data-tab');const title=pane.getAttribute('data-tab-title');const active=pane.getAttribute('data-tab-active')==='true';menu.appendChild(E('li',{'style':this.isEmptyPane(pane)?'display:none':null,'class':active?'cbi-tab':'cbi-tab-disabled','data-tab':name},E('a',{'href':'#','click':this.switchTab.bind(this)},title)));if(active)
selected=i;}
group.parentNode.insertBefore(menu,group);group.setAttribute('data-initialized',true);if(selected===null){selected=this.getActiveTabId(panes[0]);if(selected<0||selected>=panes.length||this.isEmptyPane(panes[selected])){for(let i=0;i<panes.length;i++){if(!this.isEmptyPane(panes[i])){selected=i;break;}}}
menu.childNodes[selected].classList.add('cbi-tab');menu.childNodes[selected].classList.remove('cbi-tab-disabled');panes[selected].setAttribute('data-tab-active','true');this.setActiveTabId(panes[selected],selected);}
requestAnimationFrame(L.bind(pane=>{pane.dispatchEvent(new CustomEvent('cbi-tab-active',{detail:{tab:pane.getAttribute('data-tab')}}));},this,panes[selected]));this.updateTabs(group);},isEmptyPane(pane){return dom.isEmpty(pane,n=>n.classList.contains('cbi-tab-descr'));},getPathForPane(pane){const path=[];let node=null;for(node=pane?pane.parentNode:null;node!=null&&node.hasAttribute!=null;node=node.parentNode)
{if(node.hasAttribute('data-tab'))
path.unshift(node.getAttribute('data-tab'));else if(node.hasAttribute('data-section-id'))
path.unshift(node.getAttribute('data-section-id'));}
return path.join('/');},getActiveTabState(){const page=document.body.getAttribute('data-page');const state=session.getLocalData('tab');if(L.isObject(state)&&state.page===page&&L.isObject(state.paths))
return state;session.setLocalData('tab',null);return{page:page,paths:{}};},getActiveTabId(pane){const path=this.getPathForPane(pane);return+this.getActiveTabState().paths[path]??0;},setActiveTabId(pane,tabIndex){const path=this.getPathForPane(pane);const state=this.getActiveTabState();state.paths[path]=tabIndex;return session.setLocalData('tab',state);},updateTabs(ev,root){(root??document).querySelectorAll('[data-tab-title]').forEach(L.bind((pane)=>{const menu=pane.parentNode.previousElementSibling;const tab=menu?menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))):null;const n_errors=pane.querySelectorAll('.cbi-input-invalid').length;if(!menu||!tab)
return;if(this.isEmptyPane(pane)){tab.style.display='none';tab.classList.remove('flash');}
else if(tab.style.display==='none'){tab.style.display='';requestAnimationFrame(()=>tab.classList.add('flash'));}
if(n_errors){tab.setAttribute('data-errors',n_errors);tab.setAttribute('data-tooltip',_('%d invalid field(s)').format(n_errors));tab.setAttribute('data-tooltip-style','error');}
else{tab.removeAttribute('data-errors');tab.removeAttribute('data-tooltip');}},this));},switchTab(ev){const tab=ev.target.parentNode;const name=tab.getAttribute('data-tab');const menu=tab.parentNode;const group=menu.nextElementSibling;const groupId=+group.getAttribute('data-tab-group');let index=0;ev.preventDefault();if(!tab.classList.contains('cbi-tab-disabled'))
return;menu.querySelectorAll('[data-tab]').forEach(tab=>{tab.classList.remove('cbi-tab');tab.classList.remove('cbi-tab-disabled');tab.classList.add(tab.getAttribute('data-tab')===name?'cbi-tab':'cbi-tab-disabled');});group.childNodes.forEach(pane=>{if(dom.matches(pane,'[data-tab]')){if(pane.getAttribute('data-tab')===name){pane.setAttribute('data-tab-active','true');pane.dispatchEvent(new CustomEvent('cbi-tab-active',{detail:{tab:name}}));UI.prototype.tabs.setActiveTabId(pane,index);}
else{pane.setAttribute('data-tab-active','false');}
index++;}});}}),uploadFile(path,progressStatusNode){return new Promise((resolveFn,rejectFn)=>{UI.prototype.showModal(_('Uploading file…'),[E('p',_('Please select the file to upload.')),E('div',{'style':'display:flex'},[E('div',{'class':'left','style':'flex:1'},[E('input',{type:'file',style:'display:none',change(ev){const modal=dom.parent(ev.target,'.modal');const body=modal.querySelector('p');const upload=modal.querySelector('.cbi-button-action.important');const file=ev.currentTarget.files[0];if(file==null)
return;dom.content(body,[E('ul',{},[E('li',{},['%s: %s'.format(_('Name'),file.name.replace(/^.*[\\\/]/,''))]),E('li',{},['%s: %1024mB'.format(_('Size'),file.size)])])]);upload.disabled=false;upload.focus();}}),E('button',{'class':'btn cbi-button','click':function(ev){ev.target.previousElementSibling.click();}},[_('Browse…')])]),E('div',{'class':'right','style':'flex:1'},[E('button',{'class':'btn','click':function(){UI.prototype.hideModal();rejectFn(new Error(_('Upload has been cancelled')));}},[_('Cancel')]),' ',E('button',{'class':'btn cbi-button-action important','disabled':true,'click':function(ev){const input=dom.parent(ev.target,'.modal').querySelector('input[type="file"]');if(!input.files[0])
return;const progress=E('div',{'class':'cbi-progressbar','title':'0%'},E('div',{'style':'width:0'}));UI.prototype.showModal(_('Uploading file…'),[progress]);const data=new FormData();data.append('sessionid',rpc.getSessionID());data.append('filename',path);data.append('filedata',input.files[0]);const filename=input.files[0].name;request.post(`${L.env.cgi_base}/cgi-upload`,data,{timeout:0,progress(pev){const percent=(pev.loaded/pev.total)*100;if(progressStatusNode)
progressStatusNode.data='%.2f%%'.format(percent);progress.setAttribute('title','%.2f%%'.format(percent));progress.firstElementChild.style.width='%.2f%%'.format(percent);}}).then(res=>{const reply=res.json();UI.prototype.hideModal();if(L.isObject(reply)&&reply.failure){UI.prototype.addNotification(null,E('p',_('Upload request failed: %s').format(reply.message)));rejectFn(new Error(reply.failure));}
else{reply.name=filename;resolveFn(reply);}},err=>{UI.prototype.hideModal();rejectFn(err);});}},[_('Upload')])])])]);});},pingDevice(proto,ipaddr){const target='%s://%s%s?%s'.format(proto??'http',ipaddr??window.location.host,L.resource('icons/loading.svg'),Math.random());return new Promise((resolveFn,rejectFn)=>{const img=new Image();img.onload=resolveFn;img.onerror=rejectFn;window.setTimeout(rejectFn,1000);img.src=target;});},awaitReconnect(...hosts){const ipaddrs=hosts.length?hosts:[window.location.host];window.setTimeout(L.bind(()=>{poll.add(L.bind(()=>{const tasks=[];let reachable=false;for(let i=0;i<2;i++)
for(let j=0;j<ipaddrs.length;j++)
tasks.push(this.pingDevice(i?'https':'http',ipaddrs[j]).then(ev=>{reachable=ev.target.src.replace(/^(https?:\/\/[^\/]+).*$/,'$1/')},()=>{}));return Promise.all(tasks).then(()=>{if(reachable){poll.stop();window.location=reachable;}});},this));},this),5000);},changes:baseclass.singleton({init(){if(!L.env.sessionid)
return;return uci.changes().then(L.bind(this.renderChangeIndicator,this));},setIndicator(n){if(n>0){UI.prototype.showIndicator('uci-changes','%s: %d'.format(_('Unsaved Changes'),n),L.bind(this.displayChanges,this));}
else{UI.prototype.hideIndicator('uci-changes');}},renderChangeIndicator(changes){let n_changes=0;for(const config in changes)
if(changes.hasOwnProperty(config))
n_changes+=changes[config].length;this.changes=changes;this.setIndicator(n_changes);},changeTemplates:{'add-3':'<ins>uci add %0 <strong>%3</strong> # =%2</ins>','set-3':'<ins>uci set %0.<strong>%2</strong>=%3</ins>','set-4':'<var><ins>uci set %0.%2.%3=<strong>%4</strong></ins></var>','remove-2':'<del>uci del %0.<strong>%2</strong></del>','remove-3':'<var><del>uci del %0.%2.<strong>%3</strong></del></var>','order-3':'<var>uci reorder %0.%2=<strong>%3</strong></var>','list-add-4':'<var><ins>uci add_list %0.%2.%3=<strong>%4</strong></ins></var>','list-del-4':'<var><del>uci del_list %0.%2.%3=<strong>%4</strong></del></var>','rename-3':'<var>uci rename %0.%2=<strong>%3</strong></var>','rename-4':'<var>uci rename %0.%2.%3=<strong>%4</strong></var>'},displayChanges(){const list=E('div',{'class':'uci-change-list'});const dlg=UI.prototype.showModal(`${_('Configuration')} / ${_('Changes')}`,[E('div',{'class':'cbi-section'},[E('strong',_('Legend:')),E('div',{'class':'uci-change-legend'},[E('div',{'class':'uci-change-legend-label'},[E('ins','&#160;'),' ',_('Section added')]),E('div',{'class':'uci-change-legend-label'},[E('del','&#160;'),' ',_('Section removed')]),E('div',{'class':'uci-change-legend-label'},[E('var',{},E('ins','&#160;')),' ',_('Option changed')]),E('div',{'class':'uci-change-legend-label'},[E('var',{},E('del','&#160;')),' ',_('Option removed')])]),E('br'),list,E('div',{'class':'button-row'},[E('button',{'class':'btn cbi-button','click':UI.prototype.hideModal},[_('Close')]),' ',new UIComboButton('0',{0:[_('Save & Apply')],1:[_('Apply unchecked')]},{classes:{0:'btn cbi-button cbi-button-positive important',1:'btn cbi-button cbi-button-negative important'},click:L.bind((ev,mode)=>{this.apply(mode=='0')},this)}).render(),' ',E('button',{'class':'btn cbi-button cbi-button-reset','click':L.bind(this.revert,this)},[_('Revert')])])])]);for(const config in this.changes){if(!this.changes.hasOwnProperty(config))
continue;list.appendChild(E('h5','# /etc/config/%s'.format(config)));for(let i=0,added=null;i<this.changes[config].length;i++){const chg=this.changes[config][i];const tpl=this.changeTemplates['%s-%d'.format(chg[0],chg.length)];list.appendChild(E(tpl.replace(/%([01234])/g,(m0,m1)=>{switch(+m1){case 0:return config;case 2:if(added!=null&&chg[1]==added[0])
return`@${added[1]}[-1]`;else
return chg[1];case 4:return"'%h'".format(chg[3].replace(/'/g,"'\"'\"'"));default:return chg[m1-1];}})));if(chg[0]=='add')
added=[chg[1],chg[2]];}}
list.appendChild(E('br'));dlg.classList.add('uci-dialog');},displayStatus(type,content){if(type){const message=UI.prototype.showModal('','');message.classList.add('alert-message');DOMTokenList.prototype.add.apply(message.classList,type.split(/\s+/));if(content)
dom.content(message,content);if(!this.was_polling){this.was_polling=request.poll.active();request.poll.stop();}}
else{UI.prototype.hideModal();if(this.was_polling)
request.poll.start();}},checkConnectivityAffected(){return L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr',null,'json')).then(L.bind((info)=>{if(L.isObject(info)&&Array.isArray(info.inbound_interfaces)){for(let i=0;i<info.inbound_interfaces.length;i++){const iif=info.inbound_interfaces[i];for(let j=0;this.changes&&this.changes.network&&j<this.changes.network.length;j++){const chg=this.changes.network[j];if(chg[0]=='set'&&chg[1]==iif&&((chg[2]=='disabled'&&chg[3]=='1')||chg[2]=='proto'||chg[2]=='ipaddr'||chg[2]=='netmask'))
return iif;}}}
return null;},this));},rollback(checked){if(checked){this.displayStatus('warning spinning',E('p',_('Failed to confirm apply within %ds, waiting for rollback…').format(L.env.apply_rollback)));const call=(r,data,duration)=>{if(r.status===204){UI.prototype.changes.displayStatus('warning',[E('h4',_('Configuration changes have been rolled back!')),E('p',_('The device could not be reached within %d seconds after applying the pending changes, which caused the configuration to be rolled back for safety reasons. If you believe that the configuration changes are correct nonetheless, perform an unchecked configuration apply. Alternatively, you can dismiss this warning and edit changes before attempting to apply again, or revert all pending changes to keep the currently working configuration state.').format(L.env.apply_rollback)),E('div',{'class':'button-row'},[E('button',{'class':'btn','click':L.bind(UI.prototype.changes.displayStatus,UI.prototype.changes,false)},[_('Dismiss')]),' ',E('button',{'class':'btn cbi-button-action important','click':L.bind(UI.prototype.changes.revert,UI.prototype.changes)},[_('Revert changes')]),' ',E('button',{'class':'btn cbi-button-negative important','click':L.bind(UI.prototype.changes.apply,UI.prototype.changes,false)},[_('Apply unchecked')])])]);return;}
const delay=isNaN(duration)?0:Math.max(1000-duration,0);window.setTimeout(()=>{request.request(L.url('admin/uci/confirm'),{method:'post',timeout:L.env.apply_timeout*1000,query:{sid:L.env.sessionid,token:L.env.token}}).then(call,call.bind(null,{status:0,duration:0}));},delay);};call({status:0});}
else{this.displayStatus('warning',[E('h4',_('Device unreachable!')),E('p',_('Could not regain access to the device after applying the configuration changes. You might need to reconnect if you modified network related settings such as the IP address or wireless security credentials.'))]);}},confirm(checked,deadline,override_token){let tt;let ts=Date.now();this.displayStatus('notice');if(override_token)
this.confirm_auth={token:override_token};const call=(r,data,duration)=>{if(Date.now()>=deadline){window.clearTimeout(tt);UI.prototype.changes.rollback(checked);return;}
else if(r.status===200||r.status===204){document.dispatchEvent(new CustomEvent('uci-applied'));UI.prototype.changes.setIndicator(0);UI.prototype.changes.displayStatus('notice',E('p',_('Configuration changes applied.')));window.clearTimeout(tt);window.setTimeout(()=>{window.location=window.location.href.split('#')[0];},L.env.apply_display*1000);return;}
const delay=isNaN(duration)?0:Math.max(1000-duration,0);window.setTimeout(()=>{request.request(L.url('admin/uci/confirm'),{method:'post',timeout:L.env.apply_timeout*1000,query:UI.prototype.changes.confirm_auth}).then(call,call.bind(null,{status:0,duration:0}));},delay);};const tick=()=>{const now=Date.now();UI.prototype.changes.displayStatus('notice spinning',E('p',_('Applying configuration changes… %ds').format(Math.max(Math.floor((deadline-Date.now())/1000),0))));if(now>=deadline)
return;tt=window.setTimeout(tick,1000-(now-ts));ts=now;};tick();window.setTimeout(call.bind(null,{status:0}),L.env.apply_holdoff*1000);},apply(checked){this.displayStatus('notice spinning',E('p',_('Starting configuration apply…')));(new Promise((resolveFn,rejectFn)=>{if(!checked)
return resolveFn(false);UI.prototype.changes.checkConnectivityAffected().then(affected=>{if(!affected)
return resolveFn(true);UI.prototype.changes.displayStatus('warning',[E('h4',_('Connectivity change')),E('p',_('Changes have been made to the existing connection via "%h". This could inhibit access to this device. Any IP change requires <strong>connecting to the new IP</strong> within %d seconds to retain the changes.').format(affected,L.env.apply_rollback)),E('div',{'class':'button-row'},[E('button',{'class':'btn cbi-button','click':rejectFn,},[_('Cancel')]),' ',E('button',{'class':'btn cbi-button-action important','click':resolveFn.bind(null,true)},[_('Apply, reverting in case of connectivity loss')]),' ',E('button',{'class':'btn cbi-button-negative important','click':resolveFn.bind(null,false)},[_('Apply unchecked')])])]);});})).then(checked=>{request.request(L.url('admin/uci',checked?'apply_rollback':'apply_unchecked'),{method:'post',query:{sid:L.env.sessionid,token:L.env.token}}).then(r=>{if(r.status===(checked?200:204)){let tok=null;try{tok=r.json();}catch(e){}
if(checked&&tok!==null&&typeof(tok)==='object'&&typeof(tok.token)==='string')
UI.prototype.changes.confirm_auth=tok;UI.prototype.changes.confirm(checked,Date.now()+L.env.apply_rollback*1000);}
else if(checked&&r.status===204){UI.prototype.changes.displayStatus('notice',E('p',_('There are no changes to apply')));window.setTimeout(()=>{UI.prototype.changes.displayStatus(false);},L.env.apply_display*1000);}
else{UI.prototype.changes.displayStatus('warning',E('p',_('Apply request failed with status <code>%h</code>').format(r.responseText??r.statusText??r.status)));window.setTimeout(()=>{UI.prototype.changes.displayStatus(false);},L.env.apply_display*1000);}});},this.displayStatus.bind(this,false));},revert(){this.displayStatus('notice spinning',E('p',_('Reverting configuration…')));request.request(L.url('admin/uci/revert'),{method:'post',query:{sid:L.env.sessionid,token:L.env.token}}).then(r=>{if(r.status===200){document.dispatchEvent(new CustomEvent('uci-reverted'));UI.prototype.changes.setIndicator(0);UI.prototype.changes.displayStatus('notice',E('p',_('Changes have been reverted.')));window.setTimeout(()=>{window.location=window.location.href.split('#')[0];},L.env.apply_display*1000);}
else{UI.prototype.changes.displayStatus('warning',E('p',_('Revert request failed with status <code>%h</code>').format(r.statusText??r.status)));window.setTimeout(()=>{UI.prototype.changes.displayStatus(false);},L.env.apply_display*1000);}});}}),addValidator(field,type,optional,vfunc,...events){if(type==null)
return;if(events.length==0)
events.push('blur','keyup');try{const cbiValidator=validation.create(field,type,optional,vfunc);const validatorFn=cbiValidator.validate.bind(cbiValidator);for(let i=0;i<events.length;i++)
field.addEventListener(events[i],validatorFn);validatorFn();return validatorFn;}
catch(e){}},createHandlerFn(ctx,fn,...args){if(typeof(fn)=='string')
fn=ctx[fn];if(typeof(fn)!='function')
return null;return L.bind(function(){const t=arguments[args.length].currentTarget;t.classList.add('spinning');t.disabled=true;if(t.blur)
t.blur();Promise.resolve(fn.apply(ctx,arguments)).finally(()=>{t.classList.remove('spinning');t.disabled=false;});},ctx,...args);},instantiateView(path){const className='view.%s'.format(path.replace(/\//g,'.'));return L.require(className).then(view=>{if(!(view instanceof View))
throw new TypeError('Loaded class %s is not a descendant of View'.format(className));return view;}).catch(err=>{dom.content(document.querySelector('#view'),null);L.error(err);});},menu:UIMenu,Table:UITable,AbstractElement:UIElement,Textfield:UITextfield,Textarea:UITextarea,Checkbox:UICheckbox,Select:UISelect,Dropdown:UIDropdown,DynamicList:UIDynamicList,RangeSlider:UIRangeSlider,Combobox:UICombobox,ComboButton:UIComboButton,Hiddenfield:UIHiddenfield,FileUpload:UIFileUpload});return UI;