'use strict';'require view';'require form';'require uci';'require rpc';'require ui';'require poll';'require request';'require dom';'require fs';const support_url='https://forum.openwrt.org/t/luci-attended-sysupgrade-support-thread/230552';const support_link=E('a',{href:support_url},_('this forum thread'));function detailsBlock(title,content,pre){return!content?'':''.concat('[details="',title,'"]\n',pre?'```\n':'',content,'\n',pre?'```\n':'','[/details]\n',);}
const callPackagelist=rpc.declare({object:'rpc-sys',method:'packagelist',});const callSystemBoard=rpc.declare({object:'system',method:'board',});const callUpgradeStart=rpc.declare({object:'rpc-sys',method:'upgrade_start',params:['keep'],});function get_branch(version){return version.replace('-SNAPSHOT','').split('.').slice(0,2).join('.');}
function get_revision_count(revision){return parseInt(revision.substring(1).split('-')[0]);}
return view.extend({steps:{init:[0,_('Received build request')],container_setup:[10,_('Setting up ImageBuilder')],validate_revision:[20,_('Validating revision')],validate_manifest:[30,_('Validating package selection')],calculate_packages_hash:[40,_('Calculating package hash')],building_image:[50,_('Generating firmware image')],signing_images:[95,_('Signing images')],done:[100,_('Completed generating firmware image')],failed:[100,_('Failed to generate firmware image')],download_imagebuilder:[20,_('Downloading ImageBuilder archive')],unpack_imagebuilder:[40,_('Setting Up ImageBuilder')],},request_hash:new Map(),sha256_unsigned:'',applyPackageChanges:async function(package_info){let{url,target,version,packages}=package_info;const overview_url=`${url}/api/v1/overview`;const revision_url=`${url}/api/v1/revision/${version}/${target}`;let changes,target_revision;await Promise.all([request.get(overview_url).then(response=>response.json()).then(json=>json.branches).then(branches=>branches[get_branch(version)]).then(branch=>{changes=branch.package_changes;}).catch(error=>{throw Error(`Get overview failed:<br>${overview_url}<br>${error}`);}),request.get(revision_url).then(response=>response.json()).then(json=>json.revision).then(revision=>{target_revision=get_revision_count(revision);}).catch(error=>{throw Error(`Get revision failed:<br>${revision_url}<br>${error}`);}),]);for(const change of changes){let idx=packages.indexOf(change.source);if(idx>=0&&change.revision<=target_revision){if(change.target)
packages[idx]=change.target;else
packages.splice(idx,1);}}
return packages;},selectImage:function(images,data,firmware){var filesystemFilter=function(e){return(e.filesystem==firmware.filesystem);}
var typeFilter=function(e){let efi_targets=['armsr','loongarch','x86'];let efi_capable=efi_targets.some((tgt)=>firmware.target.startsWith(tgt));if(efi_capable){if(data.efi){return(e.type=='combined-efi');}else{return(e.type=='combined');}}else{return(e.type=='sysupgrade'||e.type=='combined');}}
return images.filter(filesystemFilter).filter(typeFilter)[0];},handle200:function(response,content,data,firmware){response=response.json();let image=this.selectImage(response.images,data,firmware);if(image.name!=undefined){this.sha256_unsigned=image.sha256_unsigned;let sysupgrade_url=`${data.url}/store/${response.bin_dir}/${image.name}`;let keep=E('input',{type:'checkbox'});keep.checked=true;let fields=[_('Version'),`${response.version_number} ${response.version_code}`,_('SHA256'),image.sha256,];if(data.advanced_mode==1){fields.push(_('Profile'),response.id,_('Target'),response.target,_('Build Date'),response.build_at,_('Filename'),image.name,_('Filesystem'),image.filesystem);}
fields.push('',E('a',{href:sysupgrade_url},_('Download firmware image')));if(data.rebuilder){fields.push(_('Rebuilds'),E('div',{id:'rebuilder_status'}));}
let table=E('div',{class:'table'});for(let i=0;i<fields.length;i+=2){table.appendChild(E('tr',{class:'tr'},[E('td',{class:'td left',width:'33%'},[fields[i]]),E('td',{class:'td left'},[fields[i+1]]),]));}
let modal_body=[table,E('p',{class:'mt-2'},E('label',{class:'btn'},[keep,' ',_('Keep settings and retain the current configuration'),])),E('div',{class:'right'},[E('div',{class:'btn',click:ui.hideModal},_('Cancel')),' ',E('button',{class:'btn cbi-button cbi-button-positive important',click:ui.createHandlerFn(this,function(){this.handleInstall(sysupgrade_url,keep.checked,image.sha256);}),},_('Install firmware image')),]),];ui.showModal(_('Successfully created firmware image'),modal_body);if(data.rebuilder){this.handleRebuilder(content,data,firmware);}}},handle202:function(response){if('queue_position'in response){ui.showModal(_('Queued...'),[E('p',{class:'spinning'},_('Request in build queue position %s').format(response.queue_position)),]);}else{ui.showModal(_('Building Firmware...'),[E('p',{class:'spinning'},_('Progress: %s%% %s').format(this.steps[response.imagebuilder_status][0],this.steps[response.imagebuilder_status][1])),]);}},handleError:function(response,data,firmware,request_hash){response=response.json();const request_data={...data,request_hash:request_hash,sha256_unsigned:this.sha256_unsigned,...firmware};const request_str=JSON.stringify(request_data,null,4);if(typeof response.detail!="string"){response.detail=JSON.stringify(response.detail,null,4);}
let body=[E('p',{},[_('First, check'),' ',support_link,_('.  If you don\'t find a solution there, then report all of the information below.')]),E('button',{class:'btn cbi-button cbi-button-positive important',style:'margin-bottom: 1em; padding: 0.2em',click:ui.createHandlerFn(this,function(){var text=''.concat('Server response: %s\n\n'.format(response.detail),detailsBlock('Request Data',request_str,true),detailsBlock('STDOUT',response.stdout,true),detailsBlock('STDERR',response.stderr,true),);navigator.clipboard.writeText(text);ui.showModal(_('Data copied!'),[E('p',[_('Paste the contents of the clipboard to'),' ',support_link,'.',]),E('div',{class:'right'},[E('div',{class:'btn',click:ui.hideModal},_('Close')),]),]);}),},_('Copy error data to clipboard...')),E('p',_('Server response: %s').format(response.detail)),E('p',_('Request Data:')),E('pre',{},request_str),];if(response.stdout){body.push(E('b','STDOUT:'),E('pre',response.stdout),);}
if(response.stderr){body.push(E('b','STDERR:'),E('pre',response.stderr),);}
body.push(E('div',{class:'right'},[E('div',{class:'btn',click:ui.hideModal},_('Close')),]),);ui.showModal(_('Error building the firmware image'),body);},handleRequest:function(server,main,content,data,firmware){let request_url=`${server}/api/v1/build`;let method='POST';let local_content=content;const request_hash=this.request_hash.get(server);if(request_hash){request_url+=`/${request_hash}`;local_content={};method='GET';}
request.request(request_url,{method:method,content:local_content}).then((response)=>{switch(response.status){case 202:response=response.json();this.request_hash.set(server,response.request_hash);if(main){this.handle202(response);}else{let view=document.getElementById(server);view.innerText=`⏳ (${
        this.steps[response.imagebuilder_status][0]
       }%) ${server}`;}
break;case 200:if(main==true){poll.remove(this.pollFn);this.handle200(response,content,data,firmware);}else{poll.remove(this.rebuilder_polls[server]);response=response.json();let view=document.getElementById(server);let image=this.selectImage(response.images,data,firmware);if(image.sha256_unsigned==this.sha256_unsigned){view.innerText='✅ %s'.format(server);}else{view.innerHTML=`⚠️ ${server} (<a href="${server}/store/${
         response.bin_dir
        }/${image.name}">${_('Download')}</a>)`;}}
break;default:if(main==true){poll.remove(this.pollFn);this.handleError(response,data,firmware,request_hash);}else{poll.remove(this.rebuilder_polls[server]);document.getElementById(server).innerText='🚫 %s'.format(server);}
break;}});},handleRebuilder:function(content,data,firmware){this.rebuilder_polls={};for(let rebuilder of data.rebuilder){this.rebuilder_polls[rebuilder]=L.bind(this.handleRequest,this,rebuilder,false,content,data,firmware);poll.add(this.rebuilder_polls[rebuilder],5);document.getElementById('rebuilder_status').innerHTML+=`<p id="${rebuilder}">⏳ ${rebuilder}</p>`;}
poll.start();},handleInstall:function(url,keep,sha256){ui.showModal(_('Downloading...'),[E('p',{class:'spinning'},_('Downloading firmware from server to browser')),]);request.get(url,{headers:{'Content-Type':'application/x-www-form-urlencoded',},responseType:'blob',}).then((response)=>{let form_data=new FormData();form_data.append('sessionid',rpc.getSessionID());form_data.append('filename','/tmp/firmware.bin');form_data.append('filemode',600);form_data.append('filedata',response.blob());ui.showModal(_('Uploading...'),[E('p',{class:'spinning'},_('Uploading firmware from browser to device')),]);request.get(`${L.env.cgi_base}/cgi-upload`,{method:'PUT',content:form_data,}).then((response)=>response.json()).then((response)=>{if(response.sha256sum!=sha256){ui.showModal(_('Wrong checksum'),[E('p',_('Error during download of firmware. Please try again')),E('div',{class:'btn',click:ui.hideModal},_('Close')),]);}else{ui.showModal(_('Installing...'),[E('div',{class:'spinning'},[E('p',_('Installing the sysupgrade image...')),E('p',_('Once the image is written, the system will reboot.')
+' '+
_('This should take at least a minute, so please wait for the login screen.')),E('b',_('While you are waiting, do not unpower device!')),]),]);L.resolveDefault(callUpgradeStart(keep),{}).then((response)=>{let hosts=keep?[]:['192.168.1.1','openwrt.lan'];setTimeout(()=>{ui.awaitReconnect(...hosts);},10000);});}});});},handleCheck:function(data,firmware){this.request_hash.clear();let{url,revision,advanced_mode,branch}=data;let{version,target,profile,packages}=firmware;let candidates=[];const endpoint=version.endsWith('SNAPSHOT')?`revision/${version}/${target}`:'overview';const request_url=`${url}/api/v1/${endpoint}`;ui.showModal(_('Searching...'),[E('p',{class:'spinning'},_('Searching for an available sysupgrade of %s - %s').format(version,revision)),]);L.resolveDefault(request.get(request_url)).then((response)=>{if(!response.ok){ui.showModal(_('Error connecting to upgrade server'),[E('p',{},_('Could not reach API at "%s". Please try again later.').format(response.url)),E('pre',{},response.responseText),E('div',{class:'right'},[E('div',{class:'btn',click:ui.hideModal},_('Close')),]),]);return;}
if(version.endsWith('SNAPSHOT')){const remote_revision=response.json().revision;if(get_revision_count(revision)<get_revision_count(remote_revision)){candidates.push([version,remote_revision]);}}else{const latest=response.json().latest;latest.sort().reverse();for(let remote_version of latest){let remote_branch=get_branch(remote_version);if(version==remote_version){break;}
candidates.unshift([remote_version,null]);if(branch==remote_branch){break;}}}
if(advanced_mode==1){candidates.unshift([version,revision]);}
if(candidates.length){let s,o;let mapdata={request:{profile,version:candidates[0][0],packages:Object.keys(packages).sort(),},};let map=new form.JSONMap(mapdata,'');s=map.section(form.NamedSection,'request','','','Use defaults for the safest update');o=s.option(form.ListValue,'version','Select firmware version');for(let candidate of candidates){if(candidate[0]==version&&candidate[1]==revision){o.value(candidate[0],_('[installed] %s').format(candidate[1]?`${candidate[0]} - ${candidate[1]}`:candidate[0]));}else{o.value(candidate[0],candidate[1]?`${candidate[0]} - ${candidate[1]}`:candidate[0]);}}
if(advanced_mode==1){o=s.option(form.Value,'profile',_('Board Name / Profile'));o=s.option(form.DynamicList,'packages',_('Packages'));}
L.resolveDefault(map.render()).then((form_rendered)=>{ui.showModal(_('New firmware upgrade available'),[E('p',_('Currently running: %s - %s').format(version,revision)),form_rendered,E('div',{class:'right'},[E('div',{class:'btn',click:ui.hideModal},_('Cancel')),' ',E('button',{class:'btn cbi-button cbi-button-positive important',click:ui.createHandlerFn(this,function(){map.save().then(()=>{this.applyPackageChanges({url,target,version:mapdata.request.version,packages:mapdata.request.packages,}).then((packages)=>{const content={...firmware,packages:packages,version:mapdata.request.version,profile:mapdata.request.profile};this.pollFn=L.bind(function(){this.handleRequest(url,true,content,data,firmware);},this);poll.add(this.pollFn,5);poll.start();}).catch(error=>{ui.addNotification(null,E('p',error.message));ui.hideModal();});});}),},_('Request firmware image')),]),]);});}else{ui.showModal(_('No upgrade available'),[E('p',_('The device runs the latest firmware version %s - %s').format(version,revision)),E('div',{class:'right'},[E('div',{class:'btn',click:ui.hideModal},_('Close')),]),]);}});},load:async function(){const promises=await Promise.all([L.resolveDefault(callPackagelist(),{}),L.resolveDefault(callSystemBoard(),{}),L.resolveDefault(fs.stat('/sys/firmware/efi'),null),uci.load('attendedsysupgrade'),]);const data={system_board:promises[1],advanced_mode:uci.get_first('attendedsysupgrade','client','advanced_mode')||0,url:uci.get_first('attendedsysupgrade','server','url').replace(/\/+$/,''),branch:get_branch(promises[1].release.version),revision:promises[1].release.revision,efi:promises[2]!=null,rebuilder:uci.get_first('attendedsysupgrade','server','rebuilder'),};const firmware={client:'luci/'+promises[0].packages['luci-app-attendedsysupgrade'],packages:promises[0].packages,profile:promises[1].board_name,target:promises[1].release.target,version:promises[1].release.version,diff_packages:true,filesystem:promises[1].rootfs_type,rootfs_size_mb:uci.get('attendedsysupgrade','owut','rootfs_size'),};return[data,firmware];},render:function(response){const data=response[0];const firmware=response[1];return E('p',[E('h2',_('Attended Sysupgrade')),E('p',_('The attended sysupgrade service allows to upgrade vanilla and custom firmware images easily.')),E('p',_('This is done by building a new firmware on demand via an online service.')),E('p',_('Currently running: %s - %s').format(firmware.version,data.revision)),E('button',{class:'btn cbi-button cbi-button-positive important',click:ui.createHandlerFn(this,this.handleCheck,data,firmware),},_('Search for firmware upgrade')),]);},handleSaveApply:null,handleSave:null,handleReset:null,});