2019-09-24 09:33:21 +00:00
'use strict' ;
'require form' ;
'require rpc' ;
var callFileStat , callFileRead , callFileWrite , callFileExec , callFileRemove ;
callFileStat = rpc . declare ( {
object : 'file' ,
method : 'stat' ,
params : [ 'path' ] ,
expect : { '' : { } }
} ) ;
callFileRead = rpc . declare ( {
object : 'file' ,
method : 'read' ,
params : [ 'path' ] ,
expect : { data : '' } ,
filter : function ( s ) { return ( s || '' ) . trim ( ) }
} ) ;
callFileWrite = rpc . declare ( {
object : 'file' ,
method : 'write' ,
params : [ 'path' , 'data' ]
} ) ;
callFileExec = rpc . declare ( {
object : 'file' ,
method : 'exec' ,
params : [ 'command' , 'params' ] ,
expect : { '' : { code : - 1 } }
} ) ;
callFileRemove = rpc . declare ( {
object : 'file' ,
method : 'remove' ,
params : [ 'path' ]
} ) ;
function pingDevice ( proto , ipaddr ) {
var target = '%s://%s%s?%s' . format ( proto || 'http' , ipaddr || window . location . host , L . resource ( 'icons/loading.gif' ) , Math . random ( ) ) ;
return new Promise ( function ( resolveFn , rejectFn ) {
var img = new Image ( ) ;
img . onload = resolveFn ;
img . onerror = rejectFn ;
window . setTimeout ( rejectFn , 1000 ) ;
img . src = target ;
} ) ;
}
function awaitReconnect ( /* ... */ ) {
var ipaddrs = arguments . length ? arguments : [ window . location . host ] ;
window . setTimeout ( function ( ) {
L . Poll . add ( function ( ) {
var tasks = [ ] , reachable = false ;
for ( var i = 0 ; i < 2 ; i ++ )
for ( var j = 0 ; j < ipaddrs . length ; j ++ )
tasks . push ( pingDevice ( i ? 'https' : 'http' , ipaddrs [ j ] )
. then ( function ( ev ) { reachable = ev . target . src . replace ( /^(https?:\/\/[^\/]+).*$/ , '$1/' ) } , function ( ) { } ) ) ;
return Promise . all ( tasks ) . then ( function ( ) {
if ( reachable ) {
L . Poll . stop ( ) ;
window . location = reachable ;
}
} ) ;
} )
} , 5000 ) ;
}
function fileUpload ( node , path ) {
return new Promise ( function ( resolveFn , rejectFn ) {
L . ui . 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 : function ( ev ) {
L . dom . parent ( ev . target , '.modal' ) . querySelector ( '.cbi-button-action.important' ) . disabled = false ;
}
} ) ,
E ( 'button' , {
'class' : 'btn' ,
'click' : function ( ev ) {
ev . target . previousElementSibling . click ( ) ;
}
} , [ _ ( 'Browse…' ) ] )
] ) ,
E ( 'div' , { 'class' : 'right' , 'style' : 'flex:1' } , [
E ( 'button' , {
'class' : 'btn' ,
'click' : function ( ) {
L . ui . hideModal ( ) ;
rejectFn ( new Error ( 'Upload has been cancelled' ) ) ;
}
} , [ _ ( 'Cancel' ) ] ) ,
' ' ,
E ( 'button' , {
'class' : 'btn cbi-button-action important' ,
'disabled' : true ,
'click' : function ( ev ) {
var input = L . dom . parent ( ev . target , '.modal' ) . querySelector ( 'input[type="file"]' ) ;
if ( ! input . files [ 0 ] )
return ;
var progress = E ( 'div' , { 'class' : 'cbi-progressbar' , 'title' : '0%' } , E ( 'div' , { 'style' : 'width:0' } ) ) ;
L . ui . showModal ( _ ( 'Uploading file…' ) , [ progress ] ) ;
var data = new FormData ( ) ;
data . append ( 'sessionid' , rpc . getSessionID ( ) ) ;
data . append ( 'filename' , path ) ;
data . append ( 'filedata' , input . files [ 0 ] ) ;
L . Request . post ( '/cgi-bin/cgi-upload' , data , {
timeout : 0 ,
progress : function ( pev ) {
var percent = ( pev . loaded / pev . total ) * 100 ;
node . data = '%.2f%%' . format ( percent ) ;
progress . setAttribute ( 'title' , '%.2f%%' . format ( percent ) ) ;
progress . firstElementChild . style . width = '%.2f%%' . format ( percent ) ;
}
} ) . then ( function ( res ) {
var reply = res . json ( ) ;
L . ui . hideModal ( ) ;
if ( L . isObject ( reply ) && reply . failure ) {
L . ui . addNotification ( null , E ( 'p' , _ ( 'Upload request failed: %s' ) . format ( reply . message ) ) ) ;
rejectFn ( new Error ( reply . failure ) ) ;
}
else {
resolveFn ( reply ) ;
}
} , function ( err ) {
L . ui . hideModal ( ) ;
rejectFn ( err ) ;
} ) ;
}
} , [ _ ( 'Upload' ) ] )
] )
] )
] ) ;
} ) ;
}
function findStorageSize ( procmtd , procpart ) {
var kernsize = 0 , rootsize = 0 , wholesize = 0 ;
procmtd . split ( /\n/ ) . forEach ( function ( ln ) {
2019-09-25 14:23:02 +00:00
var match = ln . match ( /^mtd\d+: ([0-9a-f]+) [0-9a-f]+ "(.+)"$/ ) ,
size = match ? parseInt ( match [ 1 ] , 16 ) : 0 ;
2019-09-24 09:33:21 +00:00
switch ( match ? match [ 2 ] : '' ) {
case 'linux' :
case 'firmware' :
2019-09-25 14:23:02 +00:00
if ( size > wholesize )
wholesize = size ;
2019-09-24 09:33:21 +00:00
break ;
case 'kernel' :
case 'kernel0' :
2019-09-25 14:23:02 +00:00
kernsize = size ;
2019-09-24 09:33:21 +00:00
break ;
case 'rootfs' :
case 'rootfs0' :
case 'ubi' :
case 'ubi0' :
2019-09-25 14:23:02 +00:00
rootsize = size ;
2019-09-24 09:33:21 +00:00
break ;
}
} ) ;
if ( wholesize > 0 )
return wholesize ;
else if ( kernsize > 0 && rootsize > kernsize )
return kernsize + rootsize ;
procpart . split ( /\n/ ) . forEach ( function ( ln ) {
var match = ln . match ( /^\s*\d+\s+\d+\s+(\d+)\s+(\S+)$/ ) ;
if ( match ) {
var size = parseInt ( match [ 1 ] , 10 ) ;
if ( ! match [ 2 ] . match ( /\d/ ) && size > 2048 && wholesize == 0 )
wholesize = size * 1024 ;
}
} ) ;
return wholesize ;
}
var mapdata = { actions : { } , config : { } } ;
return L . view . extend ( {
load : function ( ) {
var max _mtd = 10 , max _ubi = 2 , max _ubi _vol = 4 ;
var tasks = [
callFileStat ( '/lib/upgrade/platform.sh' ) ,
callFileRead ( '/proc/sys/kernel/hostname' ) ,
callFileRead ( '/proc/mtd' ) ,
callFileRead ( '/proc/partitions' )
] ;
for ( var i = 0 ; i < max _mtd ; i ++ )
tasks . push ( callFileRead ( '/sys/devices/virtual/mtd/mtd%d/name' . format ( i ) ) ) ;
for ( var i = 0 ; i < max _ubi ; i ++ )
for ( var j = 0 ; j < max _ubi _vol ; j ++ )
tasks . push ( callFileRead ( '/sys/devices/virtual/ubi/ubi%d/ubi%d_%d/name' . format ( i , i , j ) ) ) ;
return Promise . all ( tasks ) ;
} ,
handleBackup : function ( ev ) {
var form = E ( 'form' , {
method : 'post' ,
action : '/cgi-bin/cgi-backup' ,
enctype : 'application/x-www-form-urlencoded'
} , E ( 'input' , { type : 'hidden' , name : 'sessionid' , value : rpc . getSessionID ( ) } ) ) ;
ev . currentTarget . parentNode . appendChild ( form ) ;
form . submit ( ) ;
form . parentNode . removeChild ( form ) ;
} ,
2019-09-25 17:37:27 +00:00
handleFirstboot : function ( ev ) {
2019-09-24 09:33:21 +00:00
if ( ! confirm ( _ ( 'Do you really want to erase all settings?' ) ) )
return ;
return callFileExec ( '/sbin/firstboot' , [ '-r' , '-y' ] ) . then ( function ( res ) {
if ( res . code != 0 )
return L . ui . addNotification ( null , E ( 'p' , _ ( 'The firstboot command failed with code %d' ) . format ( res . code ) ) ) ;
L . ui . showModal ( _ ( 'Erasing...' ) , [
E ( 'p' , { 'class' : 'spinning' } , _ ( 'The system is erasing the configuration partition now and will reboot itself when finished.' ) )
] ) ;
awaitReconnect ( '192.168.1.1' , 'openwrt.lan' ) ;
} ) ;
} ,
handleRestore : function ( ev ) {
return fileUpload ( ev . target , '/tmp/backup.tar.gz' )
. then ( L . bind ( function ( btn , res ) {
btn . firstChild . data = _ ( 'Checking archive…' ) ;
return callFileExec ( '/bin/tar' , [ '-tzf' , '/tmp/backup.tar.gz' ] ) ;
} , this , ev . target ) )
. then ( L . bind ( function ( btn , res ) {
if ( res . code != 0 ) {
L . ui . addNotification ( null , E ( 'p' , _ ( 'The uploaded backup archive is not readable' ) ) ) ;
return callFileRemove ( '/tmp/backup.tar.gz' ) ;
}
L . ui . showModal ( _ ( 'Apply backup?' ) , [
E ( 'p' , _ ( 'The uploaded backup archive appears to be valid and contains the files listed below. Press "Continue" to restore the backup and reboot, or "Cancel" to abort the operation.' ) ) ,
E ( 'pre' , { } , [ res . stdout ] ) ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'button' , {
'class' : 'btn' ,
'click' : L . ui . createHandlerFn ( this , function ( ev ) {
return callFileRemove ( '/tmp/backup.tar.gz' ) . finally ( L . ui . hideModal ) ;
} )
} , [ _ ( 'Cancel' ) ] ) , ' ' ,
E ( 'button' , {
'class' : 'btn cbi-button-action important' ,
'click' : L . ui . createHandlerFn ( this , 'handleRestoreConfirm' , btn )
} , [ _ ( 'Continue' ) ] )
] )
] ) ;
} , this , ev . target ) )
. finally ( L . bind ( function ( btn , input ) {
btn . firstChild . data = _ ( 'Upload archive...' ) ;
} , this , ev . target ) ) ;
} ,
handleRestoreConfirm : function ( btn , ev ) {
return callFileExec ( '/sbin/sysupgrade' , [ '--restore-backup' , '/tmp/backup.tar.gz' ] )
. then ( L . bind ( function ( btn , res ) {
if ( res . code != 0 ) {
L . ui . addNotification ( null , [
E ( 'p' , _ ( 'The restore command failed with code %d' ) . format ( res . code ) ) ,
res . stderr ? E ( 'pre' , { } , [ res . stderr ] ) : ''
] ) ;
L . raise ( 'Error' , 'Unpack failed' ) ;
}
btn . firstChild . data = _ ( 'Rebooting…' ) ;
return callFileExec ( '/sbin/reboot' ) ;
} , this , ev . target ) )
. then ( L . bind ( function ( res ) {
if ( res . code != 0 ) {
L . ui . addNotification ( null , E ( 'p' , _ ( 'The reboot command failed with code %d' ) . format ( res . code ) ) ) ;
L . raise ( 'Error' , 'Reboot failed' ) ;
}
L . ui . showModal ( _ ( 'Rebooting…' ) , [
E ( 'p' , { 'class' : 'spinning' } , _ ( 'The system is rebooting now. If the restored configuration changed the current LAN IP address, you might need to reconnect manually.' ) )
] ) ;
awaitReconnect ( window . location . host , '192.168.1.1' , 'openwrt.lan' ) ;
} , this ) )
. catch ( function ( ) { btn . firstChild . data = _ ( 'Upload archive...' ) } ) ;
} ,
handleBlock : function ( hostname , ev ) {
var mtdblock = L . dom . parent ( ev . target , '.cbi-section' ) . querySelector ( '[data-name="mtdselect"] select' ) . value ;
var form = E ( 'form' , {
'method' : 'post' ,
'action' : '/cgi-bin/cgi-download' ,
'enctype' : 'application/x-www-form-urlencoded'
} , [
E ( 'input' , { 'type' : 'hidden' , 'name' : 'sessionid' , 'value' : rpc . getSessionID ( ) } ) ,
E ( 'input' , { 'type' : 'hidden' , 'name' : 'path' , 'value' : '/dev/mtdblock%d' . format ( mtdblock ) } ) ,
E ( 'input' , { 'type' : 'hidden' , 'name' : 'filename' , 'value' : '%s.mtd%d.bin' . format ( hostname , mtdblock ) } )
] ) ;
ev . currentTarget . parentNode . appendChild ( form ) ;
form . submit ( ) ;
form . parentNode . removeChild ( form ) ;
} ,
handleSysupgrade : function ( storage _size , ev ) {
return fileUpload ( ev . target . firstChild , '/tmp/firmware.bin' )
. then ( L . bind ( function ( btn , reply ) {
btn . firstChild . data = _ ( 'Checking image…' ) ;
L . ui . showModal ( _ ( 'Checking image…' ) , [
E ( 'span' , { 'class' : 'spinning' } , _ ( 'Verifying the uploaded image file.' ) )
] ) ;
return callFileExec ( '/sbin/sysupgrade' , [ '--test' , '/tmp/firmware.bin' ] )
. then ( function ( res ) { return [ reply , res ] } ) ;
} , this , ev . target ) )
. then ( L . bind ( function ( btn , res ) {
var keep = document . querySelector ( '[data-name="keep"] input[type="checkbox"]' ) ,
force = E ( 'input' , { type : 'checkbox' } ) ,
is _invalid = ( res [ 1 ] . code != 0 ) ,
is _too _big = ( storage _size > 0 && res [ 0 ] . size > storage _size ) ,
body = [ ] ;
body . push ( E ( 'p' , _ ( 'The flash image was uploaded. Below is the checksum and file size listed, compare them with the original file to ensure data integrity. <br /> Click "Proceed" below to start the flash procedure.' ) ) ) ;
body . push ( E ( 'ul' , { } , [
res [ 0 ] . size ? E ( 'li' , { } , '%s: %1024.2mB' . format ( _ ( 'Size' ) , res [ 0 ] . size ) ) : '' ,
res [ 0 ] . checksum ? E ( 'li' , { } , '%s: %s' . format ( _ ( 'MD5' ) , res [ 0 ] . checksum ) ) : '' ,
res [ 0 ] . sha256sum ? E ( 'li' , { } , '%s: %s' . format ( _ ( 'SHA256' ) , res [ 0 ] . sha256sum ) ) : '' ,
E ( 'li' , { } , keep . checked ? _ ( 'Configuration files will be kept' ) : _ ( 'Caution: Configuration files will be erased' ) )
] ) ) ;
if ( is _invalid || is _too _big )
body . push ( E ( 'hr' ) ) ;
if ( is _too _big )
body . push ( E ( 'p' , { 'class' : 'alert-message' } , [
_ ( 'It appears that you are trying to flash an image that does not fit into the flash memory, please verify the image file!' )
] ) ) ;
if ( is _invalid )
body . push ( E ( 'p' , { 'class' : 'alert-message' } , [
res [ 1 ] . stderr ? res [ 1 ] . stderr : '' ,
res [ 1 ] . stderr ? E ( 'br' ) : '' ,
res [ 1 ] . stderr ? E ( 'br' ) : '' ,
_ ( 'The uploaded image file does not contain a supported format. Make sure that you choose the generic image format for your platform.' )
] ) ) ;
if ( is _invalid || is _too _big )
body . push ( E ( 'p' , { } , E ( 'label' , { 'class' : 'btn alert-message danger' } , [
force , ' ' , _ ( 'Force upgrade' ) ,
E ( 'br' ) , E ( 'br' ) ,
_ ( 'Select \'Force upgrade\' to flash the image even if the image format check fails. Use only if you are sure that the firmware is correct and meant for your device!' )
] ) ) ) ;
var cntbtn = E ( 'button' , {
'class' : 'btn cbi-button-action important' ,
'click' : L . ui . createHandlerFn ( this , 'handleSysupgradeConfirm' , btn , keep . checked , force . checked ) ,
'disabled' : ( is _invalid || is _too _big ) ? true : null
} , [ _ ( 'Continue' ) ] ) ;
body . push ( E ( 'div' , { 'class' : 'right' } , [
E ( 'button' , {
'class' : 'btn' ,
'click' : L . ui . createHandlerFn ( this , function ( ev ) {
return callFileRemove ( '/tmp/firmware.bin' ) . finally ( L . ui . hideModal ) ;
} )
} , [ _ ( 'Cancel' ) ] ) , ' ' , cntbtn
] ) ) ;
force . addEventListener ( 'change' , function ( ev ) {
cntbtn . disabled = ! ev . target . checked ;
} ) ;
L . ui . showModal ( _ ( 'Flash image?' ) , body ) ;
} , this , ev . target ) )
. finally ( L . bind ( function ( btn ) {
btn . firstChild . data = _ ( 'Flash image...' ) ;
} , this , ev . target ) ) ;
} ,
handleSysupgradeConfirm : function ( btn , keep , force , ev ) {
btn . firstChild . data = _ ( 'Flashing…' ) ;
L . ui . showModal ( _ ( 'Flashing…' ) , [
E ( 'p' , { 'class' : 'spinning' } , _ ( 'The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings.' ) )
] ) ;
var opts = [ ] ;
if ( ! keep )
opts . push ( '-n' ) ;
if ( force )
opts . push ( '--force' ) ;
opts . push ( '/tmp/firmware.bin' ) ;
/* Currently the sysupgrade rpc call will not return, hence no promise handling */
callFileExec ( '/sbin/sysupgrade' , opts ) ;
awaitReconnect ( window . location . host , '192.168.1.1' , 'openwrt.lan' ) ;
} ,
handleBackupList : function ( ev ) {
return callFileExec ( '/sbin/sysupgrade' , [ '--list-backup' ] ) . then ( function ( res ) {
if ( res . code != 0 ) {
L . ui . addNotification ( null , [
E ( 'p' , _ ( 'The sysupgrade command failed with code %d' ) . format ( res . code ) ) ,
res . stderr ? E ( 'pre' , { } , [ res . stderr ] ) : ''
] ) ;
L . raise ( 'Error' , 'Sysupgrade failed' ) ;
}
L . ui . showModal ( _ ( 'Backup file list' ) , [
E ( 'p' , _ ( 'Below is the determined list of files to backup. It consists of changed configuration files marked by opkg, essential base files and the user defined backup patterns.' ) ) ,
E ( 'ul' , { } , ( res . stdout || '' ) . trim ( ) . split ( /\n/ ) . map ( function ( ln ) { return E ( 'li' , { } , ln ) } ) ) ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'button' , {
'class' : 'btn' ,
'click' : L . ui . hideModal
} , [ _ ( 'Dismiss' ) ] )
] )
] , 'cbi-modal' ) ;
} ) ;
} ,
handleBackupSave : function ( m , ev ) {
return m . save ( function ( ) {
return callFileWrite ( '/etc/sysupgrade.conf' , mapdata . config . editlist . trim ( ) . replace ( /\r\n/g , '\n' ) + '\n' ) ;
} ) . then ( function ( ) {
L . ui . addNotification ( null , E ( 'p' , _ ( 'Contents have been saved.' ) ) , 'info' ) ;
} ) . catch ( function ( e ) {
L . ui . addNotification ( null , E ( 'p' , _ ( 'Unable to save contents: %s' ) . format ( e ) ) ) ;
} ) ;
} ,
render : function ( rpc _replies ) {
var has _sysupgrade = ( rpc _replies [ 0 ] . type == 'file' ) ,
hostname = rpc _replies [ 1 ] ,
procmtd = rpc _replies [ 2 ] ,
procpart = rpc _replies [ 3 ] ,
has _rootfs _data = rpc _replies . slice ( 4 ) . filter ( function ( n ) { return n == 'rootfs_data' } ) [ 0 ] ,
storage _size = findStorageSize ( procmtd , procpart ) ,
m , s , o , ss ;
m = new form . JSONMap ( mapdata , _ ( 'Flash operations' ) ) ;
m . tabbed = true ;
s = m . section ( form . NamedSection , 'actions' , _ ( 'Actions' ) ) ;
o = s . option ( form . SectionValue , 'actions' , form . NamedSection , 'actions' , 'actions' , _ ( 'Backup' ) , _ ( 'Click "Generate archive" to download a tar archive of the current configuration files.' ) ) ;
ss = o . subsection ;
o = ss . option ( form . Button , 'dl_backup' , _ ( 'Download backup' ) ) ;
o . inputstyle = 'action important' ;
o . inputtitle = _ ( 'Generate archive' ) ;
o . onclick = this . handleBackup ;
o = s . option ( form . SectionValue , 'actions' , form . NamedSection , 'actions' , 'actions' , _ ( 'Restore' ) , _ ( 'To restore configuration files, you can upload a previously generated backup archive here. To reset the firmware to its initial state, click "Perform reset" (only possible with squashfs images).' ) ) ;
ss = o . subsection ;
if ( has _rootfs _data ) {
o = ss . option ( form . Button , 'reset' , _ ( 'Reset to defaults' ) ) ;
o . inputstyle = 'negative important' ;
o . inputtitle = _ ( 'Perform reset' ) ;
2019-09-25 17:37:27 +00:00
o . onclick = this . handleFirstboot ;
2019-09-24 09:33:21 +00:00
}
o = ss . option ( form . Button , 'restore' , _ ( 'Restore backup' ) , _ ( 'Custom files (certificates, scripts) may remain on the system. To prevent this, perform a factory-reset first.' ) ) ;
o . inputstyle = 'action important' ;
o . inputtitle = _ ( 'Upload archive...' ) ;
o . onclick = L . bind ( this . handleRestore , this ) ;
2019-09-25 17:42:06 +00:00
if ( procmtd . length ) {
o = s . option ( form . SectionValue , 'actions' , form . NamedSection , 'actions' , 'actions' , _ ( 'Save mtdblock contents' ) , _ ( 'Click "Save mtdblock" to download specified mtdblock file. (NOTE: THIS FEATURE IS FOR PROFESSIONALS! )' ) ) ;
ss = o . subsection ;
o = ss . option ( form . ListValue , 'mtdselect' , _ ( 'Choose mtdblock' ) ) ;
procmtd . split ( /\n/ ) . forEach ( function ( ln ) {
var match = ln . match ( /^mtd(\d+): .+ "(.+?)"$/ ) ;
if ( match )
o . value ( match [ 1 ] , match [ 2 ] ) ;
} ) ;
2019-09-24 09:33:21 +00:00
2019-09-25 17:42:06 +00:00
o = ss . option ( form . Button , 'mtddownload' , _ ( 'Download mtdblock' ) ) ;
o . inputstyle = 'action important' ;
o . inputtitle = _ ( 'Save mtdblock' ) ;
o . onclick = L . bind ( this . handleBlock , this , hostname ) ;
}
2019-09-24 09:33:21 +00:00
o = s . option ( form . SectionValue , 'actions' , form . NamedSection , 'actions' , 'actions' , _ ( 'Flash new firmware image' ) ,
has _sysupgrade
? _ ( 'Upload a sysupgrade-compatible image here to replace the running firmware. Check "Keep settings" to retain the current configuration (requires a compatible firmware image).' )
: _ ( 'Sorry, there is no sysupgrade support present; a new firmware image must be flashed manually. Please refer to the wiki for device specific install instructions.' ) ) ;
ss = o . subsection ;
if ( has _sysupgrade ) {
o = ss . option ( form . Flag , 'keep' , _ ( 'Keep settings' ) ) ;
o . default = o . enabled ;
o = ss . option ( form . Button , 'sysupgrade' , _ ( 'Image' ) ) ;
o . inputstyle = 'action important' ;
o . inputtitle = _ ( 'Flash image...' ) ;
o . onclick = L . bind ( this . handleSysupgrade , this , storage _size ) ;
}
s = m . section ( form . NamedSection , 'config' , 'config' , _ ( 'Configuration' ) , _ ( 'This is a list of shell glob patterns for matching files and directories to include during sysupgrade. Modified files in /etc/config/ and certain other configurations are automatically preserved.' ) ) ;
s . render = L . bind ( function ( view /*, ... */ ) {
return form . NamedSection . prototype . render . apply ( this , this . varargs ( arguments , 1 ) )
. then ( L . bind ( function ( node ) {
node . appendChild ( E ( 'div' , { 'class' : 'cbi-page-actions' } , [
E ( 'button' , {
'class' : 'cbi-button cbi-button-save' ,
'click' : L . ui . createHandlerFn ( view , 'handleBackupSave' , this . map )
} , [ _ ( 'Save' ) ] )
] ) ) ;
return node ;
} , this ) ) ;
} , s , this ) ;
o = s . option ( form . Button , 'showlist' , _ ( 'Show current backup file list' ) ) ;
o . inputstyle = 'action' ;
o . inputtitle = _ ( 'Open list...' ) ;
o . onclick = L . bind ( this . handleBackupList , this ) ;
o = s . option ( form . TextValue , 'editlist' ) ;
o . forcewrite = true ;
o . rows = 30 ;
o . load = function ( section _id ) {
return callFileRead ( '/etc/sysupgrade.conf' ) ;
} ;
return m . render ( ) ;
} ,
handleSaveApply : null ,
handleSave : null ,
handleReset : null
} ) ;