2023-10-13 10:48:20 +00:00
'use strict' ;
'require view' ;
'require fs' ;
'require ui' ;
'require rpc' ;
var css = ' \
. controls { \
display : flex ; \
margin : . 5 em 0 1 em 0 ; \
flex - wrap : wrap ; \
justify - content : space - around ; \
} \
\
. controls > * { \
padding : . 25 em ; \
white - space : nowrap ; \
flex : 1 1 33 % ; \
box - sizing : border - box ; \
display : flex ; \
flex - wrap : wrap ; \
} \
\
. controls > * : first - child , \
. controls > * > label { \
flex - basis : 100 % ; \
min - width : 250 px ; \
} \
\
. controls > * : nth - child ( 2 ) , \
. controls > * : nth - child ( 3 ) { \
flex - basis : 20 % ; \
} \
\
. controls > * > . btn { \
flex - basis : 20 px ; \
text - align : center ; \
} \
\
. controls > * > * { \
flex - grow : 1 ; \
align - self : center ; \
} \
\
. controls > div > input { \
width : auto ; \
} \
\
. td . version , \
. td . size { \
white - space : nowrap ; \
} \
\
ul . deps , ul . deps ul , ul . errors { \
margin - left : 1 em ; \
} \
\
ul . deps li , ul . errors li { \
list - style : none ; \
} \
\
ul . deps li : before { \
content : "↳" ; \
display : inline - block ; \
width : 1 em ; \
margin - left : - 1 em ; \
} \
\
ul . deps li > span { \
white - space : nowrap ; \
} \
\
ul . errors li { \
color : # c44 ; \
font - size : 90 % ; \
font - weight : bold ; \
padding - left : 1.5 em ; \
} \
\
ul . errors li : before { \
content : "⚠" ; \
display : inline - block ; \
width : 1.5 em ; \
margin - left : - 1.5 em ; \
} \
' ;
var isReadonlyView = ! L . hasViewPermission ( ) || null ;
var callMountPoints = rpc . declare ( {
object : 'luci' ,
method : 'getMountPoints' ,
expect : { result : [ ] }
} ) ;
var packages = {
available : { providers : { } , pkgs : { } } ,
installed : { providers : { } , pkgs : { } }
} ;
var languages = [ 'en' ] ;
var currentDisplayMode = 'available' , currentDisplayRows = [ ] ;
function parseList ( s , dest )
{
var re = /([^\n]*)\n/g ,
pkg = null , key = null , val = null , m ;
while ( ( m = re . exec ( s ) ) !== null ) {
if ( m [ 1 ] . match ( /^\s(.*)$/ ) ) {
if ( pkg !== null && key !== null && val !== null )
val += '\n' + RegExp . $1 . trim ( ) ;
continue ;
}
if ( key !== null && val !== null ) {
switch ( key ) {
case 'package' :
pkg = { name : val } ;
break ;
case 'depends' :
case 'provides' :
var list = val . split ( /\s*,\s*/ ) ;
if ( list . length !== 1 || list [ 0 ] . length > 0 )
pkg [ key ] = list ;
break ;
case 'installed-time' :
pkg . installtime = new Date ( + val * 1000 ) ;
break ;
case 'installed-size' :
pkg . installsize = + val ;
break ;
case 'status' :
var stat = val . split ( /\s+/ ) ,
mode = stat [ 1 ] ,
installed = stat [ 2 ] ;
switch ( mode ) {
case 'user' :
case 'hold' :
pkg [ mode ] = true ;
break ;
}
switch ( installed ) {
case 'installed' :
pkg . installed = true ;
break ;
}
break ;
case 'essential' :
if ( val === 'yes' )
pkg . essential = true ;
break ;
case 'size' :
pkg . size = + val ;
break ;
case 'architecture' :
case 'auto-installed' :
case 'filename' :
case 'sha256sum' :
case 'section' :
break ;
default :
pkg [ key ] = val ;
break ;
}
key = val = null ;
}
if ( m [ 1 ] . trim ( ) . match ( /^([\w-]+)\s*:(.+)$/ ) ) {
key = RegExp . $1 . toLowerCase ( ) ;
val = RegExp . $2 . trim ( ) ;
}
else if ( pkg ) {
dest . pkgs [ pkg . name ] = pkg ;
var provides = dest . providers [ pkg . name ] ? [ ] : [ pkg . name ] ;
if ( pkg . provides )
provides . push . apply ( provides , pkg . provides ) ;
provides . forEach ( function ( p ) {
dest . providers [ p ] = dest . providers [ p ] || [ ] ;
dest . providers [ p ] . push ( pkg ) ;
} ) ;
}
}
}
function display ( pattern )
{
var src = packages [ currentDisplayMode === 'updates' ? 'installed' : currentDisplayMode ] ,
table = document . querySelector ( '#packages' ) ,
pagers = document . querySelectorAll ( '.controls > .pager' ) ,
i18n _filter = null ;
currentDisplayRows . length = 0 ;
if ( typeof ( pattern ) === 'string' && pattern . length > 0 )
pattern = new RegExp ( pattern . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) , 'ig' ) ;
switch ( document . querySelector ( 'input[name="filter_i18n"]:checked' ) . value ) {
case 'all' :
i18n _filter = /^luci-i18n-/ ;
break ;
case 'lang' :
i18n _filter = new RegExp ( '^luci-i18n-(base-.+|.+-(' + languages . join ( '|' ) + '))$' ) ;
break ;
}
for ( var name in src . pkgs ) {
var pkg = src . pkgs [ name ] ,
desc = pkg . description || '' ,
altsize = null ;
if ( ! pkg . size && packages . available . pkgs [ name ] )
altsize = packages . available . pkgs [ name ] . size ;
if ( ! desc && packages . available . pkgs [ name ] )
desc = packages . available . pkgs [ name ] . description || '' ;
desc = desc . split ( /\n/ ) ;
desc = desc [ 0 ] . trim ( ) + ( desc . length > 1 ? '…' : '' ) ;
if ( ( pattern instanceof RegExp ) &&
! name . match ( pattern ) && ! desc . match ( pattern ) )
continue ;
if ( name . indexOf ( 'luci-i18n-' ) === 0 && ( ! ( i18n _filter instanceof RegExp ) || ! name . match ( i18n _filter ) ) )
continue ;
var btn , ver ;
if ( currentDisplayMode === 'updates' ) {
var avail = packages . available . pkgs [ name ] ,
inst = packages . installed . pkgs [ name ] ;
if ( ! inst || ! inst . installed )
continue ;
if ( ! avail || compareVersion ( avail . version , pkg . version ) <= 0 )
continue ;
ver = '%s » %s' . format (
truncateVersion ( pkg . version || '-' ) ,
truncateVersion ( avail . version || '-' ) ) ;
btn = E ( 'div' , {
'class' : 'btn cbi-button-positive' ,
'data-package' : name ,
'click' : handleInstall
} , _ ( 'Upgrade…' ) ) ;
}
else if ( currentDisplayMode === 'installed' ) {
if ( ! pkg . installed )
continue ;
ver = truncateVersion ( pkg . version || '-' ) ;
btn = E ( 'div' , {
'class' : 'btn cbi-button-negative' ,
'data-package' : name ,
'click' : handleRemove
} , _ ( 'Remove…' ) ) ;
}
else {
var inst = packages . installed . pkgs [ name ] ;
ver = truncateVersion ( pkg . version || '-' ) ;
if ( ! inst || ! inst . installed )
btn = E ( 'div' , {
'class' : 'btn cbi-button-action' ,
'data-package' : name ,
'click' : handleInstall
} , _ ( 'Install…' ) ) ;
else if ( inst . installed && inst . version != pkg . version )
btn = E ( 'div' , {
'class' : 'btn cbi-button-positive' ,
'data-package' : name ,
'click' : handleInstall
} , _ ( 'Upgrade…' ) ) ;
else
btn = E ( 'div' , {
'class' : 'btn cbi-button-neutral' ,
'disabled' : 'disabled'
} , _ ( 'Installed' ) ) ;
}
name = '%h' . format ( name ) ;
desc = '%h' . format ( desc || '-' ) ;
if ( pattern ) {
name = name . replace ( pattern , '<ins>$&</ins>' ) ;
desc = desc . replace ( pattern , '<ins>$&</ins>' ) ;
}
currentDisplayRows . push ( [
name ,
ver ,
pkg . size ? '%1024mB' . format ( pkg . size )
: ( altsize ? '~%1024mB' . format ( altsize ) : '-' ) ,
desc ,
btn
] ) ;
}
currentDisplayRows . sort ( function ( a , b ) {
if ( a [ 0 ] < b [ 0 ] )
return - 1 ;
else if ( a [ 0 ] > b [ 0 ] )
return 1 ;
else
return 0 ;
} ) ;
for ( var i = 0 ; i < pagers . length ; i ++ ) {
pagers [ i ] . parentNode . style . display = '' ;
pagers [ i ] . setAttribute ( 'data-offset' , 100 ) ;
}
handlePage ( { target : pagers [ 0 ] . querySelector ( '.prev' ) } ) ;
}
function handlePage ( ev )
{
var filter = document . querySelector ( 'input[name="filter"]' ) ,
offset = + ev . target . parentNode . getAttribute ( 'data-offset' ) ,
next = ev . target . classList . contains ( 'next' ) ,
pagers = document . querySelectorAll ( '.controls > .pager' ) ;
if ( ( next && ( offset + 100 ) >= currentDisplayRows . length ) ||
( ! next && ( offset < 100 ) ) )
return ;
offset += next ? 100 : - 100 ;
for ( var i = 0 ; i < pagers . length ; i ++ ) {
pagers [ i ] . setAttribute ( 'data-offset' , offset ) ;
pagers [ i ] . querySelector ( '.text' ) . firstChild . data = currentDisplayRows . length
? _ ( 'Displaying %d-%d of %d' ) . format ( 1 + offset , Math . min ( offset + 100 , currentDisplayRows . length ) , currentDisplayRows . length )
: _ ( 'No packages' ) ;
if ( offset < 100 )
pagers [ i ] . querySelector ( '.prev' ) . setAttribute ( 'disabled' , 'disabled' ) ;
else
pagers [ i ] . querySelector ( '.prev' ) . removeAttribute ( 'disabled' ) ;
if ( ( offset + 100 ) >= currentDisplayRows . length )
pagers [ i ] . querySelector ( '.next' ) . setAttribute ( 'disabled' , 'disabled' ) ;
else
pagers [ i ] . querySelector ( '.next' ) . removeAttribute ( 'disabled' ) ;
}
var placeholder = _ ( 'No information available' ) ;
if ( filter . value )
placeholder = [
E ( 'span' , { } , _ ( 'No packages matching "<strong>%h</strong>".' ) . format ( filter . value ) ) , ' (' ,
E ( 'a' , { href : '#' , click : handleReset } , _ ( 'Reset' ) ) , ')'
] ;
cbi _update _table ( '#packages' , currentDisplayRows . slice ( offset , offset + 100 ) ,
placeholder ) ;
}
function handleMode ( ev )
{
var tab = findParent ( ev . target , 'li' ) ;
if ( tab . getAttribute ( 'data-mode' ) === currentDisplayMode )
return ;
tab . parentNode . querySelectorAll ( 'li' ) . forEach ( function ( li ) {
li . classList . remove ( 'cbi-tab' ) ;
li . classList . add ( 'cbi-tab-disabled' ) ;
} ) ;
tab . classList . remove ( 'cbi-tab-disabled' ) ;
tab . classList . add ( 'cbi-tab' ) ;
currentDisplayMode = tab . getAttribute ( 'data-mode' ) ;
display ( document . querySelector ( 'input[name="filter"]' ) . value ) ;
ev . target . blur ( ) ;
ev . preventDefault ( ) ;
}
function handleI18nFilter ( ev )
{
display ( document . querySelector ( 'input[name="filter"]' ) . value ) ;
}
function orderOf ( c )
{
if ( c === '~' )
return - 1 ;
else if ( c === '' || c >= '0' && c <= '9' )
return 0 ;
else if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) )
return c . charCodeAt ( 0 ) ;
else
return c . charCodeAt ( 0 ) + 256 ;
}
function compareVersion ( val , ref )
{
var vi = 0 , ri = 0 ,
isdigit = { 0 : 1 , 1 : 1 , 2 : 1 , 3 : 1 , 4 : 1 , 5 : 1 , 6 : 1 , 7 : 1 , 8 : 1 , 9 : 1 } ;
val = val || '' ;
ref = ref || '' ;
if ( val === ref )
return 0 ;
while ( vi < val . length || ri < ref . length ) {
var first _diff = 0 ;
while ( ( vi < val . length && ! isdigit [ val . charAt ( vi ) ] ) ||
( ri < ref . length && ! isdigit [ ref . charAt ( ri ) ] ) ) {
var vc = orderOf ( val . charAt ( vi ) ) , rc = orderOf ( ref . charAt ( ri ) ) ;
if ( vc !== rc )
return vc - rc ;
vi ++ ; ri ++ ;
}
while ( val . charAt ( vi ) === '0' )
vi ++ ;
while ( ref . charAt ( ri ) === '0' )
ri ++ ;
while ( isdigit [ val . charAt ( vi ) ] && isdigit [ ref . charAt ( ri ) ] ) {
first _diff = first _diff || ( val . charCodeAt ( vi ) - ref . charCodeAt ( ri ) ) ;
vi ++ ; ri ++ ;
}
if ( isdigit [ val . charAt ( vi ) ] )
return 1 ;
else if ( isdigit [ ref . charAt ( ri ) ] )
return - 1 ;
else if ( first _diff )
return first _diff ;
}
return 0 ;
}
function versionSatisfied ( ver , ref , vop )
{
var r = compareVersion ( ver , ref ) ;
switch ( vop ) {
case '<' :
case '<=' :
return r <= 0 ;
case '>' :
case '>=' :
return r >= 0 ;
case '<<' :
return r < 0 ;
case '>>' :
return r > 0 ;
case '=' :
return r == 0 ;
}
return false ;
}
function pkgStatus ( pkg , vop , ver , info )
{
info . errors = info . errors || [ ] ;
info . install = info . install || [ ] ;
if ( pkg . installed ) {
if ( vop && ! versionSatisfied ( pkg . version , ver , vop ) ) {
var repl = null ;
( packages . available . providers [ pkg . name ] || [ ] ) . forEach ( function ( p ) {
if ( ! repl && versionSatisfied ( p . version , ver , vop ) )
repl = p ;
} ) ;
if ( repl ) {
info . install . push ( repl ) ;
return E ( 'span' , {
'class' : 'label' ,
'data-tooltip' : _ ( 'Requires update to %h %h' )
. format ( repl . name , repl . version )
} , _ ( 'Needs upgrade' ) ) ;
}
info . errors . push ( _ ( 'The installed version of package <em>%h</em> is not compatible, require %s while %s is installed.' ) . format ( pkg . name , truncateVersion ( ver , vop ) , truncateVersion ( pkg . version ) ) ) ;
return E ( 'span' , {
'class' : 'label warning' ,
'data-tooltip' : _ ( 'Require version %h %h,\ninstalled %h' )
. format ( vop , ver , pkg . version )
} , _ ( 'Version incompatible' ) ) ;
}
return E ( 'span' , { 'class' : 'label notice' } , _ ( 'Installed' ) ) ;
}
else if ( ! pkg . missing ) {
if ( ! vop || versionSatisfied ( pkg . version , ver , vop ) ) {
info . install . push ( pkg ) ;
return E ( 'span' , { 'class' : 'label' } , _ ( 'Not installed' ) ) ;
}
info . errors . push ( _ ( 'The repository version of package <em>%h</em> is not compatible, require %s but only %s is available.' )
. format ( pkg . name , truncateVersion ( ver , vop ) , truncateVersion ( pkg . version ) ) ) ;
return E ( 'span' , {
'class' : 'label warning' ,
'data-tooltip' : _ ( 'Require version %h %h,\ninstalled %h' )
. format ( vop , ver , pkg . version )
} , _ ( 'Version incompatible' ) ) ;
}
else {
info . errors . push ( _ ( 'Required dependency package <em>%h</em> is not available in any repository.' ) . format ( pkg . name ) ) ;
return E ( 'span' , { 'class' : 'label warning' } , _ ( 'Not available' ) ) ;
}
}
function renderDependencyItem ( dep , info , flat )
{
var li = E ( 'li' ) ,
vop = dep . version ? dep . version [ 0 ] : null ,
ver = dep . version ? dep . version [ 1 ] : null ,
depends = [ ] ;
for ( var i = 0 ; dep . pkgs && i < dep . pkgs . length ; i ++ ) {
var pkg = packages . installed . pkgs [ dep . pkgs [ i ] ] ||
packages . available . pkgs [ dep . pkgs [ i ] ] ||
{ name : dep . name } ;
if ( i > 0 )
li . appendChild ( document . createTextNode ( ' | ' ) ) ;
var text = pkg . name ;
if ( pkg . installsize )
text += ' (%1024mB)' . format ( pkg . installsize ) ;
else if ( pkg . size )
text += ' (~%1024mB)' . format ( pkg . size ) ;
li . appendChild ( E ( 'span' , { 'data-tooltip' : pkg . description } ,
[ text , ' ' , pkgStatus ( pkg , vop , ver , info ) ] ) ) ;
( pkg . depends || [ ] ) . forEach ( function ( d ) {
if ( depends . indexOf ( d ) === - 1 )
depends . push ( d ) ;
} ) ;
}
if ( ! li . firstChild )
li . appendChild ( E ( 'span' , { } ,
[ dep . name , ' ' ,
pkgStatus ( { name : dep . name , missing : true } , vop , ver , info ) ] ) ) ;
if ( ! flat ) {
var subdeps = renderDependencies ( depends , info ) ;
if ( subdeps )
li . appendChild ( subdeps ) ;
}
return li ;
}
function renderDependencies ( depends , info , flat )
{
var deps = depends || [ ] ,
items = [ ] ;
info . seen = info . seen || [ ] ;
for ( var i = 0 ; i < deps . length ; i ++ ) {
var dep , vop , ver ;
if ( deps [ i ] === 'libc' )
continue ;
if ( deps [ i ] . match ( /^(.+?)\s+\((<=|>=|<<|>>|<|>|=)(.+?)\)/ ) ) {
dep = RegExp . $1 . trim ( ) ;
vop = RegExp . $2 . trim ( ) ;
ver = RegExp . $3 . trim ( ) ;
}
else {
dep = deps [ i ] . trim ( ) ;
vop = ver = null ;
}
if ( info . seen [ dep ] )
continue ;
var pkgs = [ ] ;
( packages . installed . providers [ dep ] || [ ] ) . forEach ( function ( p ) {
if ( pkgs . indexOf ( p . name ) === - 1 ) pkgs . push ( p . name ) ;
} ) ;
( packages . available . providers [ dep ] || [ ] ) . forEach ( function ( p ) {
if ( pkgs . indexOf ( p . name ) === - 1 ) pkgs . push ( p . name ) ;
} ) ;
info . seen [ dep ] = {
name : dep ,
pkgs : pkgs ,
version : [ vop , ver ]
} ;
items . push ( renderDependencyItem ( info . seen [ dep ] , info , flat ) ) ;
}
if ( items . length )
return E ( 'ul' , { 'class' : 'deps' } , items ) ;
return null ;
}
function truncateVersion ( v , op )
{
v = v . replace ( /\b(([a-f0-9]{8})[a-f0-9]{24,32})\b/ ,
'<span data-tooltip="$1">$2…</span>' ) ;
if ( ! op || op === '=' )
return v ;
return '%h %h' . format ( op , v ) ;
}
function handleReset ( ev )
{
var filter = document . querySelector ( 'input[name="filter"]' ) ;
filter . value = '' ;
display ( ) ;
}
function handleInstall ( ev )
{
var name = ev . target . getAttribute ( 'data-package' ) ,
pkg = packages . available . pkgs [ name ] ,
depcache = { } ,
size ;
if ( pkg . installsize )
size = _ ( '~%1024mB installed' ) . format ( pkg . installsize ) ;
else if ( pkg . size )
size = _ ( '~%1024mB compressed' ) . format ( pkg . size ) ;
else
size = _ ( 'unknown' ) ;
var deps = renderDependencies ( pkg . depends , depcache ) ,
tree = null , errs = null , inst = null , desc = null ;
if ( depcache . errors && depcache . errors . length ) {
errs = E ( 'ul' , { 'class' : 'errors' } ) ;
depcache . errors . forEach ( function ( err ) {
errs . appendChild ( E ( 'li' , { } , err ) ) ;
} ) ;
}
var totalsize = pkg . installsize || pkg . size || 0 ,
totalpkgs = 1 ,
suggestsize = 0 ;
if ( depcache . install && depcache . install . length )
depcache . install . forEach ( function ( ipkg ) {
totalsize += ipkg . installsize || ipkg . size || 0 ;
totalpkgs ++ ;
} ) ;
var luci _basename = pkg . name . match ( /^luci-([^-]+)-(.+)$/ ) ,
i18n _packages = [ ] ,
i18n _tree ;
if ( luci _basename && ( luci _basename [ 1 ] != 'i18n' || luci _basename [ 2 ] . indexOf ( 'base-' ) === 0 ) ) {
var i18n _filter ;
if ( luci _basename [ 1 ] == 'i18n' ) {
var basenames = [ ] ;
for ( var pkgname in packages . installed . pkgs ) {
var m = pkgname . match ( /^luci-([^-]+)-(.+)$/ ) ;
if ( m && m [ 1 ] != 'i18n' )
basenames . push ( m [ 2 ] ) ;
}
if ( basenames . length )
i18n _filter = new RegExp ( '^luci-i18n-(' + basenames . join ( '|' ) + ')-' + pkg . name . replace ( /^luci-i18n-base-/ , '' ) + '$' ) ;
}
else {
i18n _filter = new RegExp ( '^luci-i18n-' + luci _basename [ 2 ] + '-(' + languages . join ( '|' ) + ')$' ) ;
}
if ( i18n _filter ) {
for ( var pkgname in packages . available . pkgs )
if ( pkgname != pkg . name && pkgname . match ( i18n _filter ) )
i18n _packages . push ( pkgname ) ;
var i18ncache = { } ;
i18n _tree = renderDependencies ( i18n _packages , i18ncache , true ) ;
if ( i18ncache . install && i18ncache . install . length ) {
i18ncache . install . forEach ( function ( ipkg ) {
suggestsize += ipkg . installsize || ipkg . size || 0 ;
} ) ;
}
}
}
inst = E ( 'p' , [
_ ( 'Require approx. %1024mB size for %d package(s) to install.' )
. format ( totalsize , totalpkgs ) ,
' ' ,
suggestsize ? _ ( 'Suggested translations require approx. %1024mB additional space.' ) . format ( suggestsize ) : ''
] ) ;
if ( deps ) {
tree = E ( 'li' , '<strong>%s:</strong>' . format ( _ ( 'Dependencies' ) ) ) ;
tree . appendChild ( deps ) ;
}
if ( pkg . description ) {
desc = E ( 'div' , { } , [
E ( 'h5' , { } , _ ( 'Description' ) ) ,
E ( 'p' , { } , pkg . description )
] ) ;
}
ui . showModal ( _ ( 'Details for package <em>%h</em>' ) . format ( pkg . name ) , [
E ( 'ul' , { } , [
E ( 'li' , '<strong>%s:</strong> %h' . format ( _ ( 'Version' ) , pkg . version ) ) ,
E ( 'li' , '<strong>%s:</strong> %h' . format ( _ ( 'Size' ) , size ) ) ,
tree || '' ,
i18n _packages . length ? E ( 'li' , [
E ( 'strong' , [ _ ( 'Suggested translations' ) , ':' ] ) ,
i18n _tree
] ) : ''
] ) ,
desc || '' ,
errs || inst || '' ,
E ( 'div' , [
E ( 'hr' ) ,
i18n _packages . length ? E ( 'p' , [
E ( 'label' , { 'class' : 'cbi-checkbox' } , [
E ( 'input' , {
'id' : 'i18ninstall-cb' ,
'type' : 'checkbox' ,
'name' : 'i18ninstall' ,
'data-packages' : i18n _packages . join ( ' ' ) ,
'disabled' : isReadonlyView ,
'checked' : true
} ) , ' ' ,
E ( 'label' , { 'for' : 'i18ninstall-cb' } ) , ' ' ,
_ ( 'Install suggested translation packages as well' )
] )
] ) : '' ,
E ( 'p' , [
E ( 'label' , { 'class' : 'cbi-checkbox' } , [
E ( 'input' , {
'id' : 'overwrite-cb' ,
'type' : 'checkbox' ,
'name' : 'overwrite' ,
'disabled' : isReadonlyView
} ) , ' ' ,
E ( 'label' , { 'for' : 'overwrite-cb' } ) , ' ' ,
_ ( 'Allow overwriting conflicting package files' )
] )
] )
] ) ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'div' , {
'class' : 'btn' ,
'click' : ui . hideModal
} , _ ( 'Cancel' ) ) ,
' ' ,
E ( 'div' , {
'data-command' : 'install' ,
'data-package' : name ,
'class' : 'btn cbi-button-action' ,
'click' : handleOpkg ,
'disabled' : isReadonlyView
} , _ ( 'Install' ) )
] )
] ) ;
}
function handleManualInstall ( ev )
{
var name _or _url = document . querySelector ( 'input[name="install"]' ) . value ,
install = E ( 'div' , {
'class' : 'btn cbi-button-action' ,
'data-command' : 'install' ,
'data-package' : name _or _url ,
'click' : function ( ev ) {
document . querySelector ( 'input[name="install"]' ) . value = '' ;
handleOpkg ( ev ) ;
}
} , _ ( 'Install' ) ) , warning ;
if ( ! name _or _url . length ) {
return ;
}
else if ( name _or _url . indexOf ( '/' ) !== - 1 ) {
warning = E ( 'p' , { } , _ ( 'Installing packages from untrusted sources is a potential security risk! Really attempt to install <em>%h</em>?' ) . format ( name _or _url ) ) ;
}
else if ( ! packages . available . providers [ name _or _url ] ) {
warning = E ( 'p' , { } , _ ( 'The package <em>%h</em> is not available in any configured repository.' ) . format ( name _or _url ) ) ;
install = '' ;
}
else {
warning = E ( 'p' , { } , _ ( 'Really attempt to install <em>%h</em>?' ) . format ( name _or _url ) ) ;
}
ui . showModal ( _ ( 'Manually install package' ) , [
warning ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'div' , {
'click' : ui . hideModal ,
'class' : 'btn cbi-button-neutral'
} , _ ( 'Cancel' ) ) ,
' ' , install
] )
] ) ;
}
function handleConfig ( ev )
{
var conf = { } ;
ui . showModal ( _ ( 'OPKG Configuration' ) , [
E ( 'p' , { 'class' : 'spinning' } , _ ( 'Loading configuration data…' ) )
] ) ;
fs . list ( '/etc/opkg' ) . then ( function ( partials ) {
var files = [ '/etc/opkg.conf' ] ;
for ( var i = 0 ; i < partials . length ; i ++ )
if ( partials [ i ] . type == 'file' && partials [ i ] . name . match ( /\.conf$/ ) )
files . push ( '/etc/opkg/' + partials [ i ] . name ) ;
return Promise . all ( files . map ( function ( file ) {
return fs . read ( file )
. then ( L . bind ( function ( conf , file , res ) { conf [ file ] = res } , this , conf , file ) )
. catch ( function ( err ) {
ui . addNotification ( null , E ( 'p' , { } , [ _ ( 'Unable to read %s: %s' ) . format ( file , err ) ] ) ) ;
ui . hideModal ( ) ;
throw err ;
} ) ;
} ) ) ;
} ) . then ( function ( ) {
var body = [
E ( 'p' , { } , _ ( 'Below is a listing of the various configuration files used by <em>opkg</em>. Use <em>opkg.conf</em> for global settings and <em>customfeeds.conf</em> for custom repository entries. The configuration in the other files may be changed but is usually not preserved by <em>sysupgrade</em>.' ) )
] ;
Object . keys ( conf ) . sort ( ) . forEach ( function ( file ) {
body . push ( E ( 'h5' , { } , '%h' . format ( file ) ) ) ;
body . push ( E ( 'textarea' , {
'name' : file ,
'rows' : Math . max ( Math . min ( L . toArray ( conf [ file ] . match ( /\n/g ) ) . length , 10 ) , 3 )
} , '%h' . format ( conf [ file ] ) ) ) ;
} ) ;
body . push ( E ( 'div' , { 'class' : 'right' } , [
E ( 'div' , {
'class' : 'btn cbi-button-neutral' ,
'click' : ui . hideModal
} , _ ( 'Cancel' ) ) ,
' ' ,
E ( 'div' , {
'class' : 'btn cbi-button-positive' ,
'click' : function ( ev ) {
var data = { } ;
findParent ( ev . target , '.modal' ) . querySelectorAll ( 'textarea[name]' )
. forEach ( function ( textarea ) {
data [ textarea . getAttribute ( 'name' ) ] = textarea . value
} ) ;
ui . showModal ( _ ( 'OPKG Configuration' ) , [
E ( 'p' , { 'class' : 'spinning' } , _ ( 'Saving configuration data…' ) )
] ) ;
Promise . all ( Object . keys ( data ) . map ( function ( file ) {
return fs . write ( file , data [ file ] ) . catch ( function ( err ) {
ui . addNotification ( null , E ( 'p' , { } , [ _ ( 'Unable to save %s: %s' ) . format ( file , err ) ] ) ) ;
} ) ;
} ) ) . then ( ui . hideModal ) ;
} ,
'disabled' : isReadonlyView
} , _ ( 'Save' ) ) ,
] ) ) ;
ui . showModal ( _ ( 'OPKG Configuration' ) , body ) ;
} ) ;
}
function handleRemove ( ev )
{
var name = ev . target . getAttribute ( 'data-package' ) ,
pkg = packages . installed . pkgs [ name ] ,
avail = packages . available . pkgs [ name ] || { } ,
size , desc ;
if ( avail . installsize )
size = _ ( '~%1024mB installed' ) . format ( avail . installsize ) ;
else if ( avail . size )
size = _ ( '~%1024mB compressed' ) . format ( avail . size ) ;
else
size = _ ( 'unknown' ) ;
if ( avail . description ) {
desc = E ( 'div' , { } , [
E ( 'h5' , { } , _ ( 'Description' ) ) ,
E ( 'p' , { } , avail . description )
] ) ;
}
ui . showModal ( _ ( 'Remove package <em>%h</em>' ) . format ( pkg . name ) , [
E ( 'ul' , { } , [
E ( 'li' , '<strong>%s:</strong> %h' . format ( _ ( 'Version' ) , pkg . version ) ) ,
E ( 'li' , '<strong>%s:</strong> %h' . format ( _ ( 'Size' ) , size ) )
] ) ,
desc || '' ,
E ( 'div' , { 'style' : 'display:flex; justify-content:space-between; flex-wrap:wrap' } , [
E ( 'label' , { 'class' : 'cbi-checkbox' , 'style' : 'float:left' } , [
E ( 'input' , { 'id' : 'autoremove-cb' , 'type' : 'checkbox' , 'checked' : 'checked' , 'name' : 'autoremove' , 'disabled' : isReadonlyView } ) , ' ' ,
E ( 'label' , { 'for' : 'autoremove-cb' } ) , ' ' ,
_ ( 'Automatically remove unused dependencies' )
] ) ,
E ( 'div' , { 'style' : 'flex-grow:1' , 'class' : 'right' } , [
E ( 'div' , {
'class' : 'btn' ,
'click' : ui . hideModal
} , _ ( 'Cancel' ) ) ,
' ' ,
E ( 'div' , {
'data-command' : 'remove' ,
'data-package' : name ,
'class' : 'btn cbi-button-negative' ,
'click' : handleOpkg ,
'disabled' : isReadonlyView
} , _ ( 'Remove' ) )
] )
] )
] ) ;
}
function handleOpkg ( ev )
{
return new Promise ( function ( resolveFn , rejectFn ) {
var cmd = ev . target . getAttribute ( 'data-command' ) ,
pkg = ev . target . getAttribute ( 'data-package' ) ,
rem = document . querySelector ( 'input[name="autoremove"]' ) ,
owr = document . querySelector ( 'input[name="overwrite"]' ) ,
i18n = document . querySelector ( 'input[name="i18ninstall"]' ) ;
var dlg = ui . showModal ( _ ( 'Executing package manager' ) , [
E ( 'p' , { 'class' : 'spinning' } ,
_ ( 'Waiting for the <em>opkg %h</em> command to complete…' ) . format ( cmd ) )
] ) ;
var argv = [ cmd , '--force-removal-of-dependent-packages' ] ;
if ( rem && rem . checked )
argv . push ( '--autoremove' ) ;
if ( owr && owr . checked )
argv . push ( '--force-overwrite' ) ;
if ( i18n && i18n . checked )
argv . push . apply ( argv , i18n . getAttribute ( 'data-packages' ) . split ( ' ' ) ) ;
if ( pkg != null )
argv . push ( pkg ) ;
fs . exec _direct ( '/usr/libexec/opkg-call' , argv , 'json' ) . then ( function ( res ) {
dlg . removeChild ( dlg . lastChild ) ;
if ( res . stdout )
dlg . appendChild ( E ( 'pre' , [ res . stdout ] ) ) ;
if ( res . stderr ) {
dlg . appendChild ( E ( 'h5' , _ ( 'Errors' ) ) ) ;
dlg . appendChild ( E ( 'pre' , { 'class' : 'errors' } , [ res . stderr ] ) ) ;
}
if ( res . code !== 0 )
dlg . appendChild ( E ( 'p' , _ ( 'The <em>opkg %h</em> command failed with code <code>%d</code>.' ) . format ( cmd , ( res . code & 0xff ) || - 1 ) ) ) ;
dlg . appendChild ( E ( 'div' , { 'class' : 'right' } ,
E ( 'div' , {
'class' : 'btn' ,
'click' : L . bind ( function ( res ) {
if ( ui . menu && ui . menu . flushCache )
ui . menu . flushCache ( ) ;
ui . hideModal ( ) ;
updateLists ( ) ;
if ( res . code !== 0 )
rejectFn ( new Error ( res . stderr || 'opkg error %d' . format ( res . code ) ) ) ;
else
resolveFn ( res ) ;
} , this , res )
} , _ ( 'Dismiss' ) ) ) ) ;
} ) . catch ( function ( err ) {
ui . addNotification ( null , E ( 'p' , _ ( 'Unable to execute <em>opkg %s</em> command: %s' ) . format ( cmd , err ) ) ) ;
ui . hideModal ( ) ;
} ) ;
} ) ;
}
function handleUpload ( ev )
{
var path = '/tmp/upload.ipk' ;
return ui . uploadFile ( path ) . then ( L . bind ( function ( btn , res ) {
ui . showModal ( _ ( 'Manually install package' ) , [
E ( 'p' , { } , _ ( 'Installing packages from untrusted sources is a potential security risk! Really attempt to install <em>%h</em>?' ) . format ( res . name ) ) ,
E ( 'ul' , { } , [
res . size ? E ( 'li' , { } , '%s: %1024.2mB' . format ( _ ( 'Size' ) , res . size ) ) : '' ,
res . checksum ? E ( 'li' , { } , '%s: %s' . format ( _ ( 'MD5' ) , res . checksum ) ) : '' ,
res . sha256sum ? E ( 'li' , { } , '%s: %s' . format ( _ ( 'SHA256' ) , res . sha256sum ) ) : ''
] ) ,
E ( 'div' , { 'class' : 'right' } , [
E ( 'div' , {
'click' : function ( ev ) {
ui . hideModal ( ) ;
fs . remove ( path ) ;
} ,
'class' : 'btn cbi-button-neutral'
} , _ ( 'Cancel' ) ) , ' ' ,
E ( 'div' , {
'class' : 'btn cbi-button-action' ,
'data-command' : 'install' ,
'data-package' : path ,
'click' : function ( ev ) {
handleOpkg ( ev ) . finally ( function ( ) {
fs . remove ( path )
} ) ;
}
} , _ ( 'Install' ) )
] )
] ) ;
} , this , ev . target ) ) ;
}
function downloadLists ( )
{
return Promise . all ( [
callMountPoints ( ) ,
fs . exec _direct ( '/usr/libexec/opkg-call' , [ 'list-available' ] ) ,
fs . exec _direct ( '/usr/libexec/opkg-call' , [ 'list-installed' ] )
] ) ;
}
function updateLists ( data )
{
cbi _update _table ( '#packages' , [ ] ,
E ( 'div' , { 'class' : 'spinning' } , _ ( 'Loading package information…' ) ) ) ;
packages . available = { providers : { } , pkgs : { } } ;
packages . installed = { providers : { } , pkgs : { } } ;
return ( data ? Promise . resolve ( data ) : downloadLists ( ) ) . then ( function ( data ) {
var pg = document . querySelector ( '.cbi-progressbar' ) ,
mount = L . toArray ( data [ 0 ] . filter ( function ( m ) { return m . mount == '/' || m . mount == '/overlay' } ) )
. sort ( function ( a , b ) { return a . mount > b . mount } ) [ 0 ] || { size : 0 , free : 0 } ;
pg . firstElementChild . style . width = Math . floor ( mount . size ? ( ( 100 / mount . size ) * mount . free ) : 100 ) + '%' ;
pg . setAttribute ( 'title' , '%s (%1024mB)' . format ( pg . firstElementChild . style . width , mount . free ) ) ;
parseList ( data [ 1 ] , packages . available ) ;
parseList ( data [ 2 ] , packages . installed ) ;
for ( var pkgname in packages . installed . pkgs )
if ( pkgname . indexOf ( 'luci-i18n-base-' ) === 0 )
languages . push ( pkgname . substring ( 15 ) ) ;
display ( document . querySelector ( 'input[name="filter"]' ) . value ) ;
} ) ;
}
var inputTimeout = null ;
function handleInput ( ev ) {
if ( inputTimeout !== null )
window . clearTimeout ( inputTimeout ) ;
inputTimeout = window . setTimeout ( function ( ) {
display ( ev . target . value ) ;
} , 250 ) ;
}
return view . extend ( {
load : function ( ) {
return downloadLists ( ) ;
} ,
render : function ( listData ) {
var query = decodeURIComponent ( L . toArray ( location . search . match ( /\bquery=([^=]+)\b/ ) ) [ 1 ] || '' ) ;
var view = E ( [ ] , [
E ( 'style' , { 'type' : 'text/css' } , [ css ] ) ,
2023-10-13 11:13:30 +00:00
E ( 'h2' , { } , _ ( 'Blue Merle' ) ) ,
2023-10-13 10:48:20 +00:00
E ( 'div' , { 'class' : 'controls' } , [
E ( 'div' , { } , [
2023-10-13 11:13:30 +00:00
E ( 'label' , { } , _ ( 'IMEI' ) + ':' ) ,
2023-10-13 10:48:20 +00:00
E ( 'span' , { 'class' : 'control-group' } , [
2023-10-13 11:13:30 +00:00
E ( 'input' , { 'type' : 'text' , 'name' : 'filter' , 'placeholder' : _ ( 'e.g. 31428392718429' ) , 'minlength' : 14 , 'maxlenght' : 14 , 'required' : true , 'value' : query , 'input' : handleInput } ) ,
2023-10-13 10:48:20 +00:00
E ( 'button' , { 'class' : 'btn cbi-button' , 'click' : handleReset } , [ _ ( 'Clear' ) ] )
] )
] ) ,
E ( 'div' , { } , [
E ( 'label' , { } , _ ( 'Actions' ) + ':' ) , ' ' ,
E ( 'span' , { 'class' : 'control-group' } , [
2023-10-13 11:13:30 +00:00
E ( 'button' , { 'class' : 'btn cbi-button-positive' , 'data-command' : 'update' , 'click' : handleOpkg , 'disabled' : isReadonlyView } , [ _ ( 'SIM swap…' ) ] ) , ' ' ,
E ( 'button' , { 'class' : 'btn cbi-button-action' , 'click' : handleUpload , 'disabled' : isReadonlyView } , [ _ ( 'IMEI change…' ) ] ) , ' ' ,
E ( 'button' , { 'class' : 'btn cbi-button-neutral' , 'click' : handleConfig } , [ _ ( 'Shred config…' ) ] )
2023-10-13 10:48:20 +00:00
] )
] )
] )
2023-10-13 11:13:30 +00:00
2023-10-13 10:48:20 +00:00
] ) ;
requestAnimationFrame ( function ( ) {
updateLists ( listData )
} ) ;
return view ;
} ,
handleSave : null ,
handleSaveApply : null ,
handleReset : null
} ) ;