@@ -5,6 +5,7 @@ import type { ScrollBoxRenderable } from "@opentui/core";
55import type { ProcessManager } from "./process/manager" ;
66import { useServices , type DisplayItem } from "./ui/hooks/useServices" ;
77import { useLogs , type SearchMatch } from "./ui/hooks/useLogs" ;
8+ import { formatBytes , formatCpu , generateSparkline } from "./process/resources" ;
89
910interface AppProps {
1011 manager : ProcessManager ;
@@ -100,6 +101,9 @@ export function App(props: AppProps) {
100101 // Service details panel
101102 const [ showDetails , setShowDetails ] = createSignal ( false ) ;
102103
104+ // Resource graph view
105+ const [ showResourceGraph , setShowResourceGraph ] = createSignal ( false ) ;
106+
103107 // Spinner animation state
104108 const [ spinnerFrame , setSpinnerFrame ] = createSignal ( 0 ) ;
105109
@@ -569,6 +573,12 @@ export function App(props: AppProps) {
569573 setShowDetails ( true ) ;
570574 event . preventDefault ( ) ;
571575 break ;
576+
577+ // Resource graph toggle
578+ case "m" :
579+ setShowResourceGraph ( ( prev ) => ! prev ) ;
580+ event . preventDefault ( ) ;
581+ break ;
572582 }
573583 } ,
574584 ) ;
@@ -633,6 +643,9 @@ export function App(props: AppProps) {
633643 const indent = serviceItem . group ? " " : "" ;
634644 const restartBadge =
635645 service . restartCount > 0 ? `↻${ service . restartCount } ` : "" ;
646+ const resources = service . resources ;
647+ const isRunning =
648+ service . status === "running" || service . status === "healthy" ;
636649 return (
637650 < box height = { 1 } paddingLeft = { 1 } paddingRight = { 1 } flexDirection = "row" >
638651 < text > { indent } </ text >
@@ -650,6 +663,14 @@ export function App(props: AppProps) {
650663 < Show when = { restartBadge } >
651664 < text fg = "yellow" > { restartBadge } </ text >
652665 </ Show >
666+ < Show when = { isRunning && resources } >
667+ < text fg = { resources ! . cpu > 80 ? "red" : resources ! . cpu > 50 ? "yellow" : "cyan" } >
668+ { formatCpu ( resources ! . cpu ) . padStart ( 5 ) }
669+ </ text >
670+ < text > </ text >
671+ < text fg = "cyan" > { formatBytes ( resources ! . memory ) . padStart ( 7 ) } </ text >
672+ < text > </ text >
673+ </ Show >
653674 < text fg = "gray" > { service . port ? `:${ service . port } ` : "" } </ text >
654675 < text > </ text >
655676 < text fg = "gray" > { formatUptime ( service . startedAt ) } </ text >
@@ -666,28 +687,92 @@ export function App(props: AppProps) {
666687 { /* Log panel */ }
667688 < box flexGrow = { 1 } flexDirection = "column" borderStyle = "rounded" borderColor = "gray" >
668689 < box height = { 1 } paddingLeft = { 1 } flexDirection = "row" >
669- < text fg = "cyan" attributes = { TextAttributes . BOLD } >
670- Logs{ " " }
671- { viewMode ( ) === "single" && selectedService ( )
672- ? `(${ selectedService ( ) ! . name } )`
673- : "(all)" }
674- </ text >
690+ < Show
691+ when = { showResourceGraph ( ) }
692+ fallback = {
693+ < text fg = "cyan" attributes = { TextAttributes . BOLD } >
694+ Logs{ " " }
695+ { viewMode ( ) === "single" && selectedService ( )
696+ ? `(${ selectedService ( ) ! . name } )`
697+ : "(all)" }
698+ </ text >
699+ }
700+ >
701+ < text fg = "cyan" attributes = { TextAttributes . BOLD } >
702+ Resources{ " " }
703+ { selectedService ( ) ? `(${ selectedService ( ) ! . name } )` : "" }
704+ </ text >
705+ </ Show >
675706 < box flexGrow = { 1 } />
676- < Show when = { isSearchActive ( ) } >
707+ < Show when = { ! showResourceGraph ( ) && isSearchActive ( ) } >
677708 < text fg = "yellow" >
678709 /{ searchQuery ( ) } [{ currentMatchIndex ( ) + 1 } /{ searchMatches ( ) . length } ]{ " " }
679710 </ text >
680711 </ Show >
681- < Show when = { searchMode ( ) } >
712+ < Show when = { ! showResourceGraph ( ) && searchMode ( ) } >
682713 < text fg = "cyan" > /{ searchInput ( ) } _</ text >
683714 </ Show >
684- < Show when = { ! searchMode ( ) && ! following ( ) && scrollInfo ( ) . total > 0 } >
715+ < Show when = { ! showResourceGraph ( ) && ! searchMode ( ) && ! following ( ) && scrollInfo ( ) . total > 0 } >
685716 < text fg = "gray" >
686717 { scrollInfo ( ) . current } /{ scrollInfo ( ) . total } { " " }
687718 </ text >
688719 </ Show >
689- < text fg = { following ( ) ? "green" : "gray" } > { following ( ) ? "[follow]" : "[scroll]" } </ text >
720+ < Show when = { ! showResourceGraph ( ) } >
721+ < text fg = { following ( ) ? "green" : "gray" } > { following ( ) ? "[follow]" : "[scroll]" } </ text >
722+ </ Show >
723+ < Show when = { showResourceGraph ( ) } >
724+ < text fg = "cyan" > [m] logs</ text >
725+ </ Show >
690726 </ box >
727+
728+ { /* Resource graph view */ }
729+ < Show when = { showResourceGraph ( ) && selectedService ( ) } >
730+ { ( ( ) => {
731+ const service = selectedService ( ) ! ;
732+ const history = props . manager . getResourceHistory ( service . name ) ;
733+ const cpuValues = history . map ( ( h ) => h . cpu ) ;
734+ const memValues = history . map ( ( h ) => h . memory / ( 1024 * 1024 ) ) ; // Convert to MB for display
735+ const resources = service . resources ;
736+
737+ return (
738+ < box flexDirection = "column" padding = { 1 } flexGrow = { 1 } >
739+ < text fg = "yellow" attributes = { TextAttributes . BOLD } >
740+ CPU Usage
741+ </ text >
742+ < box height = { 1 } flexDirection = "row" >
743+ < text fg = "cyan" > { generateSparkline ( cpuValues , 50 ) } </ text >
744+ < text > </ text >
745+ < text fg = { resources && resources . cpu > 80 ? "red" : resources && resources . cpu > 50 ? "yellow" : "green" } >
746+ { resources ? formatCpu ( resources . cpu ) : "N/A" }
747+ </ text >
748+ </ box >
749+ < text > </ text >
750+ < text fg = "yellow" attributes = { TextAttributes . BOLD } >
751+ Memory Usage
752+ </ text >
753+ < box height = { 1 } flexDirection = "row" >
754+ < text fg = "magenta" > { generateSparkline ( memValues , 50 ) } </ text >
755+ < text > </ text >
756+ < text fg = "green" > { resources ? formatBytes ( resources . memory ) : "N/A" } </ text >
757+ </ box >
758+ < text > </ text >
759+ < text fg = "gray" >
760+ History: { history . length } samples ({ history . length } s)
761+ </ text >
762+ < Show when = { resources } >
763+ < text fg = "gray" >
764+ Memory %: { resources ! . memoryPercent . toFixed ( 1 ) } % of system
765+ </ text >
766+ </ Show >
767+ < box flexGrow = { 1 } />
768+ < text fg = "gray" > Press [m] to return to logs</ text >
769+ </ box >
770+ ) ;
771+ } ) ( ) }
772+ </ Show >
773+
774+ { /* Logs view */ }
775+ < Show when = { ! showResourceGraph ( ) } >
691776 < scrollbox
692777 ref = { ( el : ScrollBoxRenderable ) => ( scrollboxRef = el ) }
693778 flexGrow = { 1 }
@@ -785,6 +870,7 @@ export function App(props: AppProps) {
785870 </ For >
786871 </ box >
787872 </ scrollbox >
873+ </ Show >
788874 </ box >
789875 </ box >
790876
@@ -803,6 +889,8 @@ export function App(props: AppProps) {
803889 < text > lear </ text >
804890 < text fg = "gray" > [f]</ text >
805891 < text > ollow </ text >
892+ < text fg = "gray" > [m]</ text >
893+ < text > onitor </ text >
806894 < text fg = "gray" > | </ text >
807895 < text fg = "gray" > [?]</ text >
808896 < text > help </ text >
@@ -834,7 +922,7 @@ export function App(props: AppProps) {
834922 < text > Space Toggle group | i Service info</ text >
835923 < text > </ text >
836924 < text fg = "yellow" > Logs</ text >
837- < text > Tab Toggle view | c Clear | f Follow</ text >
925+ < text > Tab Toggle view | c Clear | f Follow | m Monitor </ text >
838926 < text > g/G Top/bottom | PgUp/PgDn | Ctrl+u/d</ text >
839927 < text > e Export service logs | E Export all logs</ text >
840928 < text > </ text >
0 commit comments