3939 .log-entry .warning {color : var (--warn )}
4040 .log-entry .error {color : var (--error )}
4141 .log-entry .info {color : var (--txt2 )}
42- # terminal {display : none;flex : 1 ;background : var (--bg );border : 1px solid var (--border );border-radius : 0.5rem ;padding : 1rem ;overflow-y : auto;font-family : 'Courier New' , monospace;color : var (--txt );font-size : 0.875rem }
43- # terminal .active {display : block}
42+ # desktop {display : none;flex : 1 ;background : var (--bg );border : 1px solid var (--border );border-radius : 0.5rem ;overflow : hidden;position : relative}
43+ # desktop .active {display : flex}
44+ # screen {width : 100% ;height : 100% ;border : none}
4445 .modal {position : fixed;inset : 0 ;background : rgba (0 , 0 , 0 , .8 );display : none;align-items : center;justify-content : center;z-index : 1000 }
4546 .modal .active {display : flex}
4647 .modal-content {background : var (--bg2 );border : 1px solid var (--border );border-radius : 0.5rem ;padding : 2rem ;max-width : 500px ;width : 90% ;animation : slideUp .3s }
7071 </ header >
7172 < div class ="container ">
7273 < div class ="card " id ="statusCard ">
73- < div class ="status-text " id ="statusText "> Initializing GravelBox...</ div >
74+ < div class ="status-text " id ="statusText "> Initializing GravelBox Desktop ...</ div >
7475 < div class ="progress-container ">
7576 < div class ="progress-bar " id ="progressBar " style ="width:0% "> </ div >
7677 < div class ="progress-text " id ="progressText "> 0%</ div >
8081 < div class ="card-header "> System Console</ div >
8182 < div class ="console-output " id ="consoleOutput "> </ div >
8283 </ div >
83- < div id ="terminal " > </ div >
84+ < div id ="desktop " > < div id =" screen " > </ div > </ div >
8485 </ div >
8586 < div class ="modal " id ="exportModal ">
8687 < div class ="modal-content ">
115116 const prog = ( p , m ) => { el . bar . style . width = `${ p } %` ; el . pct . textContent = `${ Math . floor ( p ) } %` ; if ( m ) { el . stat . textContent = m ; log ( m , 'info' ) } } ;
116117 const exportWS = async ( n ) => { try { log ( 'Exporting...' , 'info' ) ; const db = await new Promise ( ( r , j ) => { const q = indexedDB . open ( IDB ) ; q . onsuccess = ( ) => r ( q . result ) ; q . onerror = ( ) => j ( q . error ) } ) ; const tx = db . transaction ( [ 'blocks' ] , 'readonly' ) ; const st = tx . objectStore ( 'blocks' ) ; const d = await new Promise ( ( r , j ) => { const q = st . getAll ( ) ; q . onsuccess = ( ) => r ( q . result ) ; q . onerror = ( ) => j ( q . error ) } ) ; const exp = { v :1 , ts :new Date ( ) . toISOString ( ) , name :n , data :d } ; const blob = new Blob ( [ JSON . stringify ( exp ) ] , { type :'application/json' } ) ; const a = document . createElement ( 'a' ) ; a . href = URL . createObjectURL ( blob ) ; a . download = `${ n } .wolf` ; document . body . appendChild ( a ) ; a . click ( ) ; document . body . removeChild ( a ) ; URL . revokeObjectURL ( a . href ) ; log ( `Exported: ${ n } .wolf (${ ( blob . size / 1024 / 1024 ) . toFixed ( 2 ) } MB)` , 'success' ) ; db . close ( ) } catch ( e ) { log ( `Export failed: ${ e . message } ` , 'error' ) } } ;
117118 const importWS = async ( f ) => { try { log ( 'Importing...' , 'info' ) ; const txt = await f . text ( ) ; const imp = JSON . parse ( txt ) ; if ( ! imp . v || ! imp . data ) throw new Error ( 'Invalid .wolf file' ) ; log ( `Importing: ${ imp . name } ` , 'info' ) ; const db = await new Promise ( ( r , j ) => { const q = indexedDB . open ( IDB ) ; q . onsuccess = ( ) => r ( q . result ) ; q . onerror = ( ) => j ( q . error ) } ) ; const tx = db . transaction ( [ 'blocks' ] , 'readwrite' ) ; const st = tx . objectStore ( 'blocks' ) ; await new Promise ( ( r , j ) => { const q = st . clear ( ) ; q . onsuccess = ( ) => r ( ) ; q . onerror = ( ) => j ( q . error ) } ) ; for ( const i of imp . data ) await new Promise ( ( r , j ) => { const q = st . add ( i ) ; q . onsuccess = ( ) => r ( ) ; q . onerror = ( ) => j ( q . error ) } ) ; db . close ( ) ; log ( 'Imported. Reloading...' , 'success' ) ; setTimeout ( ( ) => window . location . reload ( ) , 2000 ) } catch ( e ) { log ( `Import failed: ${ e . message } ` , 'error' ) } } ;
118- const runCmd = async ( c , d ) => { log ( d , 'info' ) ; try { await cx . run ( "/bin/bash" , [ "-c" , c ] , { env :[ "HOME=/root" , "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" , "DEBIAN_FRONTEND=noninteractive" ] , cwd :"/root" , uid :0 , gid :0 } ) } catch ( e ) { log ( `Failed: ${ e . message } ` , 'warning' ) } } ;
119119 const loadDisk = async ( ) => { const m = [ { n :'WebSocket' , t :30000 , f :( ) => CheerpX . CloudDevice . create ( "wss://disks.webvm.io/debian_large_20230522_5044875331.ext2" ) } , { n :'HTTP' , t :45000 , f :( ) => CheerpX . HttpBytesDevice . create ( "https://disks.webvm.io/debian_mini_20230519_5022088024.ext2" ) } ] ; for ( const x of m ) { try { log ( `Connecting via ${ x . n } ...` , 'warning' ) ; const d = await Promise . race ( [ x . f ( ) , new Promise ( ( _ , j ) => setTimeout ( ( ) => j ( new Error ( 'timeout' ) ) , x . t ) ) ] ) ; log ( `${ x . n } connected` , 'success' ) ; return d } catch ( e ) { log ( `${ x . n } failed: ${ e . message } ` , 'error' ) ; if ( x === m [ m . length - 1 ] ) throw e } } } ;
120- const init = async ( ) => { try { log ( 'Starting GravelBox' , 'success' ) ; if ( ! window . crossOriginIsolated || typeof SharedArrayBuffer === 'undefined' ) { log ( 'Waiting for isolation...' , 'warning' ) ; for ( let i = 0 ; i < 20 ; i ++ ) { await new Promise ( r => setTimeout ( r , 500 ) ) ; if ( window . crossOriginIsolated ) break } if ( ! window . crossOriginIsolated ) throw new Error ( 'Isolation failed' ) } log ( 'Isolation OK' , 'success' ) ; prog ( 10 , 'Loading disk...' ) ; log ( 'Creating storage...' , 'info' ) ; idb = await CheerpX . IDBDevice . create ( IDB ) ; log ( 'Storage ready' , 'success' ) ; const disk = await loadDisk ( ) ; prog ( 40 , 'Building filesystem...' ) ; log ( 'Building overlay...' , 'info' ) ; const ovr = await CheerpX . OverlayDevice . create ( disk , idb ) ; log ( 'Overlay ready' , 'success' ) ; const web = await CheerpX . WebDevice . create ( "" ) ; prog ( 60 , 'Starting VM...' ) ; log ( 'Booting VM...' , 'info' ) ; cx = await CheerpX . Linux . create ( { mounts :[ { type :"ext2" , path :"/" , dev :ovr } , { type :"dir" , path :"/app" , dev :web } , { type :"devs" , path :"/dev" } ] } ) ; log ( 'VM started' , 'success' ) ; const oc = cx . console ; cx . console = { write :d => { const t = new TextDecoder ( ) . decode ( d ) ; if ( t . trim ( ) ) log ( t . trim ( ) , 'info' ) } } ; await runCmd ( "curl https://github.com/WolfTech-Innovations/WolfEther/releases/download/Node_x64_RL1.7-LINUX/wolfether-node -o wolfether" , "Downloading WolfEther" ) ; prog ( 95 , 'Installing...' ) ; await runCmd ( "cp wolfether /usr/local/bin/wolfether && chmod +x /usr/local/bin/wolfether" , "Installing WolfEther" ) ; cx . console = oc ; prog ( 100 , 'Ready' ) ; log ( 'Launching WolfEther...' , 'success' ) ; el . exp . disabled = false ; await new Promise ( r => setTimeout ( r , 1500 ) ) ; el . stc . classList . add ( 'hidden' ) ; el . con . classList . add ( 'hidden' ) ; el . term . classList . add ( 'active' ) ; cx . setConsole ( el . term ) ; await cx . run ( "/usr/local/bin/wolfether" , [ ] , { env :[ "HOME=/root" , "USER=root" , "SHELL=/bin/bash" , "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ] , cwd :"/root" , uid :0 , gid :0 } ) } catch ( e ) { log ( `Fatal: ${ e . message } ` , 'error' ) ; prog ( 0 , 'Failed' ) } } ;
121- document . addEventListener ( 'DOMContentLoaded' , ( ) => { el = { stat :$ ( 'statusText' ) , bar :$ ( 'progressBar' ) , pct :$ ( 'progressText' ) , out :$ ( 'consoleOutput' ) , stc :$ ( 'statusCard' ) , con :$ ( 'consoleCard' ) , term :$ ( 'terminal ' ) , exp :$ ( 'exportBtn' ) , imp :$ ( 'importBtn' ) , exM :$ ( 'exportModal' ) , imM :$ ( 'importModal' ) , exN :$ ( 'exportName' ) , imF :$ ( 'importFile' ) } ; el . exp . onclick = ( ) => el . exM . classList . add ( 'active' ) ; el . imp . onclick = ( ) => el . imM . classList . add ( 'active' ) ; $ ( 'cancelExport' ) . onclick = ( ) => el . exM . classList . remove ( 'active' ) ; $ ( 'cancelImport' ) . onclick = ( ) => el . imM . classList . remove ( 'active' ) ; $ ( 'confirmExport' ) . onclick = async ( ) => { const n = el . exN . value . trim ( ) || 'workspace' ; el . exM . classList . remove ( 'active' ) ; await exportWS ( n ) } ; $ ( 'confirmImport' ) . onclick = async ( ) => { const f = el . imF . files [ 0 ] ; if ( ! f ) { log ( 'Select a .wolf file' , 'warning' ) ; return } el . imM . classList . remove ( 'active' ) ; await importWS ( f ) } ; setTimeout ( init , 1000 ) } ) ;
120+ const init = async ( ) => { try { log ( 'Starting GravelBox Desktop' , 'success' ) ; if ( ! window . crossOriginIsolated || typeof SharedArrayBuffer === 'undefined' ) { log ( 'Waiting for isolation...' , 'warning' ) ; for ( let i = 0 ; i < 20 ; i ++ ) { await new Promise ( r => setTimeout ( r , 500 ) ) ; if ( window . crossOriginIsolated ) break } if ( ! window . crossOriginIsolated ) throw new Error ( 'Isolation failed' ) } log ( 'Isolation OK' , 'success' ) ; prog ( 10 , 'Loading disk...' ) ; log ( 'Creating storage...' , 'info' ) ; idb = await CheerpX . IDBDevice . create ( IDB ) ; log ( 'Storage ready' , 'success' ) ; const disk = await loadDisk ( ) ; prog ( 40 , 'Building filesystem...' ) ; log ( 'Building overlay...' , 'info' ) ; const ovr = await CheerpX . OverlayDevice . create ( disk , idb ) ; log ( 'Overlay ready' , 'success' ) ; const web = await CheerpX . WebDevice . create ( "" ) ; prog ( 60 , 'Starting VM...' ) ; log ( 'Booting VM...' , 'info' ) ; cx = await CheerpX . Linux . create ( { mounts :[ { type :"ext2" , path :"/" , dev :ovr } , { type :"dir" , path :"/app" , dev :web } , { type :"devs" , path :"/dev" } , { type :"devs" , path :"/dev/shm" } , { type :"proc" , path :"/proc" } ] } ) ; log ( 'VM started' , 'success' ) ; prog ( 80 , 'Starting Desktop...' ) ; el . exp . disabled = false ; await new Promise ( r => setTimeout ( r , 1000 ) ) ; prog ( 100 , 'Desktop Ready' ) ; log ( 'Launching Desktop Environment...' , 'success' ) ; await new Promise ( r => setTimeout ( r , 500 ) ) ; el . stc . classList . add ( 'hidden' ) ; el . con . classList . add ( 'hidden' ) ; el . desk . classList . add ( 'active' ) ; cx . setDisplay ( el . screen ) ; await cx . run ( "/bin/sh" , [ "-c" , "startx" ] , { env :[ "HOME=/home/user" , "USER=user" , "DISPLAY=:0" ] , cwd :"/home/user" , uid :1000 , gid :1000 } ) } catch ( e ) { log ( `Fatal: ${ e . message } ` , 'error' ) ; prog ( 0 , 'Failed' ) } } ;
121+ document . addEventListener ( 'DOMContentLoaded' , ( ) => { el = { stat :$ ( 'statusText' ) , bar :$ ( 'progressBar' ) , pct :$ ( 'progressText' ) , out :$ ( 'consoleOutput' ) , stc :$ ( 'statusCard' ) , con :$ ( 'consoleCard' ) , desk :$ ( 'desktop' ) , screen : $ ( 'screen ') , exp :$ ( 'exportBtn' ) , imp :$ ( 'importBtn' ) , exM :$ ( 'exportModal' ) , imM :$ ( 'importModal' ) , exN :$ ( 'exportName' ) , imF :$ ( 'importFile' ) } ; el . exp . onclick = ( ) => el . exM . classList . add ( 'active' ) ; el . imp . onclick = ( ) => el . imM . classList . add ( 'active' ) ; $ ( 'cancelExport' ) . onclick = ( ) => el . exM . classList . remove ( 'active' ) ; $ ( 'cancelImport' ) . onclick = ( ) => el . imM . classList . remove ( 'active' ) ; $ ( 'confirmExport' ) . onclick = async ( ) => { const n = el . exN . value . trim ( ) || 'workspace' ; el . exM . classList . remove ( 'active' ) ; await exportWS ( n ) } ; $ ( 'confirmImport' ) . onclick = async ( ) => { const f = el . imF . files [ 0 ] ; if ( ! f ) { log ( 'Select a .wolf file' , 'warning' ) ; return } el . imM . classList . remove ( 'active' ) ; await importWS ( f ) } ; setTimeout ( init , 1000 ) } ) ;
122122 </ script >
123123</ body >
124- </ html >
124+ </ html >
0 commit comments