@@ -410,6 +410,117 @@ export class SystemService {
410410 }
411411 }
412412
413+ async getDebugInfo ( ) : Promise < string > {
414+ const appVersion = SystemService . getAppVersion ( )
415+ const environment = process . env . NODE_ENV || 'unknown'
416+
417+ const [ systemInfo , services , internetStatus , versionCheck ] = await Promise . all ( [
418+ this . getSystemInfo ( ) ,
419+ this . getServices ( { installedOnly : false } ) ,
420+ this . getInternetStatus ( ) . catch ( ( ) => null ) ,
421+ this . checkLatestVersion ( ) . catch ( ( ) => null ) ,
422+ ] )
423+
424+ const lines : string [ ] = [
425+ 'Project NOMAD Debug Info' ,
426+ '========================' ,
427+ `App Version: ${ appVersion } ` ,
428+ `Environment: ${ environment } ` ,
429+ ]
430+
431+ if ( systemInfo ) {
432+ const { cpu, mem, os, disk, fsSize, uptime, graphics } = systemInfo
433+
434+ lines . push ( '' )
435+ lines . push ( 'System:' )
436+ if ( os . distro ) lines . push ( ` OS: ${ os . distro } ` )
437+ if ( os . hostname ) lines . push ( ` Hostname: ${ os . hostname } ` )
438+ if ( os . kernel ) lines . push ( ` Kernel: ${ os . kernel } ` )
439+ if ( os . arch ) lines . push ( ` Architecture: ${ os . arch } ` )
440+ if ( uptime ?. uptime ) lines . push ( ` Uptime: ${ this . _formatUptime ( uptime . uptime ) } ` )
441+
442+ lines . push ( '' )
443+ lines . push ( 'Hardware:' )
444+ if ( cpu . brand ) {
445+ lines . push ( ` CPU: ${ cpu . brand } (${ cpu . cores } cores)` )
446+ }
447+ if ( mem . total ) {
448+ const total = this . _formatBytes ( mem . total )
449+ const used = this . _formatBytes ( mem . total - ( mem . available || 0 ) )
450+ const available = this . _formatBytes ( mem . available || 0 )
451+ lines . push ( ` RAM: ${ total } total, ${ used } used, ${ available } available` )
452+ }
453+ if ( graphics . controllers && graphics . controllers . length > 0 ) {
454+ for ( const gpu of graphics . controllers ) {
455+ const vram = gpu . vram ? ` (${ gpu . vram } MB VRAM)` : ''
456+ lines . push ( ` GPU: ${ gpu . model } ${ vram } ` )
457+ }
458+ } else {
459+ lines . push ( ' GPU: None detected' )
460+ }
461+
462+ // Disk info — try disk array first, fall back to fsSize
463+ const diskEntries = disk . filter ( ( d ) => d . totalSize > 0 )
464+ if ( diskEntries . length > 0 ) {
465+ for ( const d of diskEntries ) {
466+ const size = this . _formatBytes ( d . totalSize )
467+ const type = d . tran ?. toUpperCase ( ) || ( d . rota ? 'HDD' : 'SSD' )
468+ lines . push ( ` Disk: ${ size } , ${ Math . round ( d . percentUsed ) } % used, ${ type } ` )
469+ }
470+ } else if ( fsSize . length > 0 ) {
471+ const realFs = fsSize . filter ( ( f ) => f . fs . startsWith ( '/dev/' ) )
472+ const seen = new Set < number > ( )
473+ for ( const f of realFs ) {
474+ if ( seen . has ( f . size ) ) continue
475+ seen . add ( f . size )
476+ lines . push ( ` Disk: ${ this . _formatBytes ( f . size ) } , ${ Math . round ( f . use ) } % used` )
477+ }
478+ }
479+ }
480+
481+ const installed = services . filter ( ( s ) => s . installed )
482+ lines . push ( '' )
483+ if ( installed . length > 0 ) {
484+ lines . push ( 'Installed Services:' )
485+ for ( const svc of installed ) {
486+ lines . push ( ` ${ svc . friendly_name } (${ svc . service_name } ): ${ svc . status } ` )
487+ }
488+ } else {
489+ lines . push ( 'Installed Services: None' )
490+ }
491+
492+ if ( internetStatus !== null ) {
493+ lines . push ( '' )
494+ lines . push ( `Internet Status: ${ internetStatus ? 'Online' : 'Offline' } ` )
495+ }
496+
497+ if ( versionCheck ?. success ) {
498+ const updateMsg = versionCheck . updateAvailable
499+ ? `Yes (${ versionCheck . latestVersion } available)`
500+ : `No (${ versionCheck . currentVersion } is latest)`
501+ lines . push ( `Update Available: ${ updateMsg } ` )
502+ }
503+
504+ return lines . join ( '\n' )
505+ }
506+
507+ private _formatUptime ( seconds : number ) : string {
508+ const days = Math . floor ( seconds / 86400 )
509+ const hours = Math . floor ( ( seconds % 86400 ) / 3600 )
510+ const minutes = Math . floor ( ( seconds % 3600 ) / 60 )
511+ if ( days > 0 ) return `${ days } d ${ hours } h ${ minutes } m`
512+ if ( hours > 0 ) return `${ hours } h ${ minutes } m`
513+ return `${ minutes } m`
514+ }
515+
516+ private _formatBytes ( bytes : number , decimals = 1 ) : string {
517+ if ( bytes === 0 ) return '0 Bytes'
518+ const k = 1024
519+ const sizes = [ 'Bytes' , 'KB' , 'MB' , 'GB' , 'TB' ]
520+ const i = Math . floor ( Math . log ( bytes ) / Math . log ( k ) )
521+ return parseFloat ( ( bytes / Math . pow ( k , i ) ) . toFixed ( decimals ) ) + ' ' + sizes [ i ]
522+ }
523+
413524 async updateSetting ( key : KVStoreKey , value : any ) : Promise < void > {
414525 if ( ( value === '' || value === undefined || value === null ) && KV_STORE_SCHEMA [ key ] === 'string' ) {
415526 await KVStore . clearValue ( key )
0 commit comments