@@ -59,8 +59,6 @@ func NewModel() Model {
5959 vp := viewport .New (0 , 0 )
6060 vp .SetContent ("Loading..." )
6161
62- // config.Load() now returns (Config, []Tab), we only need []Tab here
63- // but the signature of Load changed in previous step, so we need to adapt
6462 _ , tabs := config .Load ()
6563
6664 return Model {
@@ -145,21 +143,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
145143
146144func (m Model ) View () string {
147145 header := m .renderTabs (m .tabs , m .active , m .width )
148- summary := m .renderSummary (m .metrics , m .width )
149- snapshot := m .renderInfoLine (m .system .Snapshot , m .width )
150- disk := m .renderInfoLine (m .system .Disk , m .width )
151- net := m .renderInfoLine (m .system .Net , m .width )
146+ metricsRow := m .renderMetricsRow (m .metrics , m .width )
147+ systemRow := m .renderSystemRow (m .system , m .width )
152148 title := m .renderContentTitle (m .tabs [m .active ].Title , m .width )
153149 content := m .styles .ContentBox .Width (m .width ).Render (m .viewport .View ())
154150 footer := m .renderFooter (m .statusLine , spinnerFrames [m .spinnerIdx ], m .width )
155151
156152 return lipgloss .JoinVertical (
157153 lipgloss .Left ,
158154 header ,
159- summary ,
160- snapshot ,
161- disk ,
162- net ,
155+ metricsRow ,
156+ systemRow ,
163157 title ,
164158 content ,
165159 footer ,
@@ -213,7 +207,101 @@ func runCommandCmd(t config.Tab) tea.Cmd {
213207 }
214208}
215209
216- // Rendering helpers (unchanged)
210+ // Rendering helpers
211+
212+ // renderMetricsRow renders the top row of sparklines and current values
213+ func (m Model ) renderMetricsRow (history monitor.MetricHistory , width int ) string {
214+ if width <= 0 {
215+ return ""
216+ }
217+
218+ // Helper to render a single metric block with color
219+ renderBlock := func (label string , valStr string , data []float64 , min , max float64 , isPercent bool ) string {
220+ // Determine color based on latest value
221+ var color lipgloss.Style
222+ if len (data ) > 0 {
223+ last := data [len (data )- 1 ]
224+ // Normalize value for color mapping
225+ param := last
226+ if ! isPercent {
227+ // efficient approximation for load/net: reasonable max
228+ // load: max 4.0 (green), 8.0 (yellow), >8.0 (red)
229+ // net: max 1MB/s (green), 10MB/s (yellow), >10MB/s (red)
230+ // This is heuristic, percent is easier
231+ if max > 0 {
232+ param = (last / max ) * 100
233+ }
234+ }
235+
236+ if param < 50 {
237+ color = m .styles .Green
238+ } else if param < 80 {
239+ color = m .styles .Yellow
240+ } else {
241+ color = m .styles .Red
242+ }
243+ } else {
244+ color = m .styles .Processing
245+ }
246+
247+ sl := sparkline (data , min , max )
248+ // Colorize the sparkline and the value
249+ return fmt .Sprintf ("%s %s %s" , label , color .Render (valStr ), color .Render (sl ))
250+ }
251+
252+ var blocks []string
253+
254+ // CPU
255+ if len (history .CPU ) > 0 {
256+ val := history .CPU [len (history .CPU )- 1 ]
257+ blocks = append (blocks , renderBlock ("CPU" , fmt .Sprintf ("%0.0f%%" , val ), history .CPU , 0 , 100 , true ))
258+ }
259+
260+ // MEM
261+ if len (history .Mem ) > 0 {
262+ val := history .Mem [len (history .Mem )- 1 ]
263+ blocks = append (blocks , renderBlock ("MEM" , fmt .Sprintf ("%0.0f%%" , val ), history .Mem , 0 , 100 , true ))
264+ }
265+
266+ // LOAD (heuristic color: <1.0 green, <high yellow, >high red)
267+ if len (history .Load ) > 0 {
268+ val := history .Load [len (history .Load )- 1 ]
269+ max := maxFloat (history .Load )
270+ if max < 2.0 {
271+ max = 2.0
272+ } // Minimum scale for load
273+
274+ // Custom logic for load color
275+ var color lipgloss.Style
276+ if val < 1.0 {
277+ color = m .styles .Green
278+ } else if val < 4.0 {
279+ color = m .styles .Yellow
280+ } else {
281+ color = m .styles .Red
282+ }
283+
284+ sl := sparkline (history .Load , 0 , max )
285+ blocks = append (blocks , fmt .Sprintf ("LOAD %s %s" , color .Render (fmt .Sprintf ("%0.2f" , val )), color .Render (sl )))
286+ }
287+
288+ // NET
289+ if len (history .Net ) > 0 {
290+ val := history .Net [len (history .Net )- 1 ]
291+ max := maxFloat (history .Net )
292+ if max < 1 {
293+ max = 1
294+ }
295+ blocks = append (blocks , renderBlock ("NET" , monitor .FormatRate (val ), history .Net , 0 , max , false ))
296+ }
297+
298+ if len (blocks ) == 0 {
299+ return m .styles .Summary .Width (width ).Render ("Waiting for metrics..." )
300+ }
301+
302+ row := strings .Join (blocks , " " )
303+ return m .styles .Summary .Width (width ).Render (row )
304+ }
217305
218306func (m Model ) renderTabs (tabs []config.Tab , active , width int ) string {
219307 if width <= 0 {
@@ -308,53 +396,28 @@ func (m Model) renderTabs(tabs []config.Tab, active, width int) string {
308396 return m .styles .Header .Width (width ).Render (row )
309397}
310398
311- func (m Model ) renderFooter (status , spinner string , width int ) string {
312- help := "q:quit tab/shift+tab:next/prev up/down/pgup/pgdn:scroll t:theme"
313- if status != "" {
314- help = spinner + " " + status + " | " + help
315- } else if spinner != "" {
316- help = spinner + " " + help
399+ func (m Model ) renderSystemRow (info monitor.SystemInfo , width int ) string {
400+ if width <= 0 {
401+ return ""
317402 }
318- return m .styles .Footer .Width (width ).Render (help )
319- }
320403
321- func (m Model ) renderSummary (history monitor.MetricHistory , width int ) string {
322- parts := make ([]string , 0 , 4 )
323- if len (history .Load ) > 0 {
324- max := maxFloat (history .Load )
325- if max < 1 {
326- max = 1
327- }
328- parts = append (parts , fmt .Sprintf ("LOAD %s %0.2f" , sparkline (history .Load , 0 , max ), history .Load [len (history .Load )- 1 ]))
404+ var parts []string
405+ if info .Disk != "" {
406+ parts = append (parts , info .Disk )
329407 }
330- if len (history .CPU ) > 0 {
331- parts = append (parts , fmt .Sprintf ("CPU %s %0.0f%%" , sparkline (history .CPU , 0 , 100 ), history .CPU [len (history .CPU )- 1 ]))
332- }
333- if len (history .Mem ) > 0 {
334- parts = append (parts , fmt .Sprintf ("MEM %s %0.0f%%" , sparkline (history .Mem , 0 , 100 ), history .Mem [len (history .Mem )- 1 ]))
335- }
336- if len (history .Net ) > 0 {
337- max := maxFloat (history .Net )
338- if max < 1 {
339- max = 1
340- }
341- parts = append (parts , fmt .Sprintf ("NET %s %s" , sparkline (history .Net , 0 , max ), monitor .FormatRate (history .Net [len (history .Net )- 1 ])))
408+ if info .Net != "" {
409+ parts = append (parts , info .Net )
342410 }
343- row := strings .Join (parts , " | " )
344- if row == "" {
345- row = "METRICS unavailable (missing commands)"
411+ if info .Uptime != "" {
412+ parts = append (parts , info .Uptime )
346413 }
347- return m .styles .Summary .Width (width ).Render (row )
348- }
349414
350- func (m Model ) renderInfoLine (text string , width int ) string {
351- if width <= 0 {
415+ if len (parts ) == 0 {
352416 return ""
353417 }
354- if strings .TrimSpace (text ) == "" {
355- text = " "
356- }
357- return m .styles .Info .Width (width ).Render (text )
418+
419+ row := strings .Join (parts , " " )
420+ return m .styles .Info .Width (width ).Render (row )
358421}
359422
360423func (m Model ) renderContentTitle (title string , width int ) string {
@@ -365,6 +428,16 @@ func (m Model) renderContentTitle(title string, width int) string {
365428 return m .styles .Summary .Width (width ).Render (label )
366429}
367430
431+ func (m Model ) renderFooter (status , spinner string , width int ) string {
432+ help := "q:quit tab/shift+tab:next/prev up/down/pgup/pgdn:scroll t:theme"
433+ if status != "" {
434+ help = spinner + " " + status + " | " + help
435+ } else if spinner != "" {
436+ help = spinner + " " + help
437+ }
438+ return m .styles .Footer .Width (width ).Render (help )
439+ }
440+
368441func sparkline (values []float64 , min , max float64 ) string {
369442 if len (values ) == 0 {
370443 return ""
0 commit comments