@@ -2,8 +2,10 @@ package tui
22
33import (
44 "context"
5+ "encoding/json"
56 "errors"
67 "fmt"
8+ "sort"
79 "strings"
810 "time"
911
@@ -253,9 +255,7 @@ func (s *uiState) handleServiceSelection(row int, immediate bool) {
253255 }
254256
255257 if immediate {
256- s .queueUpdate (func () {
257- s .detailsView .SetText (formatServiceDetails (& svc ))
258- })
258+ s .showServiceDefinition (s .currentCluster , svc .Name )
259259 return
260260 }
261261
@@ -267,9 +267,7 @@ func (s *uiState) handleServiceSelection(row int, immediate bool) {
267267 }
268268 s .detailTimer = nil
269269 s .mutex .Unlock ()
270- s .queueUpdate (func () {
271- s .detailsView .SetText (formatServiceDetails (& svc ))
272- })
270+ s .showServiceDefinition (s .currentCluster , svc .Name )
273271 })
274272
275273 s .mutex .Lock ()
@@ -423,7 +421,8 @@ func (s *uiState) searchServices(query string) bool {
423421 if svc == nil {
424422 continue
425423 }
426- if strings .Contains (strings .ToLower (svc .Name ), query ) {
424+ fields := []string {svc .Name , svc .Image , svc .CPU , svc .Memory }
425+ if containsQuery (strings .Join (fields , " " ), query ) {
427426 row := idx + 1
428427 s .queueUpdate (func () {
429428 s .serviceTable .Select (row , 0 )
@@ -434,3 +433,145 @@ func (s *uiState) searchServices(query string) bool {
434433 }
435434 return false
436435}
436+
437+ func (s * uiState ) showServiceDefinition (clusterName , serviceName string ) {
438+ clusterName = strings .TrimSpace (clusterName )
439+ serviceName = strings .TrimSpace (serviceName )
440+ if clusterName == "" || serviceName == "" {
441+ s .setServiceDetailsText ("Select a service to inspect details" )
442+ return
443+ }
444+
445+ key := makeServiceDefinitionKey (clusterName , serviceName )
446+ s .mutex .Lock ()
447+ cached := s .serviceDefinitions [key ]
448+ s .serviceDefinitionSeq ++
449+ seq := s .serviceDefinitionSeq
450+ s .currentServiceDefinition = key
451+ s .mutex .Unlock ()
452+
453+ if cached != "" {
454+ s .queueUpdate (func () {
455+ s .detailsView .SetText (cached )
456+ })
457+ return
458+ }
459+
460+ loadingText := fmt .Sprintf ("Loading definition for %s…" , serviceName )
461+ s .queueUpdate (func () {
462+ s .detailsView .SetText (loadingText )
463+ })
464+ s .setStatus (fmt .Sprintf ("[yellow]Loading definition for %q…" , serviceName ))
465+
466+ go s .fetchServiceDefinition (clusterName , serviceName , key , seq )
467+ }
468+
469+ func (s * uiState ) fetchServiceDefinition (clusterName , serviceName , key string , seq int ) {
470+ clusterCfg := s .conf .Oscar [clusterName ]
471+ if clusterCfg == nil {
472+ s .setStatus (fmt .Sprintf ("[red]Cluster %q configuration not found" , clusterName ))
473+ return
474+ }
475+
476+ def , err := service .GetService (clusterCfg , serviceName )
477+ if err != nil {
478+ s .setStatus (fmt .Sprintf ("[red]Failed to load definition for %q: %v" , serviceName , err ))
479+ return
480+ }
481+
482+ rendered , err := formatServiceDefinition (def )
483+ if err != nil {
484+ s .setStatus (fmt .Sprintf ("[red]Failed to format definition for %q: %v" , serviceName , err ))
485+ return
486+ }
487+
488+ s .mutex .Lock ()
489+ if seq != s .serviceDefinitionSeq {
490+ s .mutex .Unlock ()
491+ return
492+ }
493+ s .serviceDefinitions [key ] = rendered
494+ active := s .currentServiceDefinition
495+ s .mutex .Unlock ()
496+
497+ if active == key {
498+ s .queueUpdate (func () {
499+ s .detailsView .SetText (rendered )
500+ })
501+ s .setStatus (fmt .Sprintf ("[green]Loaded definition for %q" , serviceName ))
502+ }
503+ }
504+
505+ func makeServiceDefinitionKey (clusterName , serviceName string ) string {
506+ return fmt .Sprintf ("%s\x00 %s" , clusterName , serviceName )
507+ }
508+
509+ func formatServiceDefinition (svc * types.Service ) (string , error ) {
510+ if svc == nil {
511+ return "" , nil
512+ }
513+
514+ data , err := json .Marshal (svc )
515+ if err != nil {
516+ return "" , err
517+ }
518+ var val interface {}
519+ if err := json .Unmarshal (data , & val ); err != nil {
520+ return "" , err
521+ }
522+ builder := & strings.Builder {}
523+ colorizeJSON (builder , val , 0 )
524+ return builder .String (), nil
525+ }
526+
527+ func colorizeJSON (builder * strings.Builder , val interface {}, level int ) {
528+ indent := strings .Repeat (" " , level )
529+ switch v := val .(type ) {
530+ case map [string ]interface {}:
531+ keys := make ([]string , 0 , len (v ))
532+ for k := range v {
533+ keys = append (keys , k )
534+ }
535+ sort .Strings (keys )
536+ builder .WriteString ("{\n " )
537+ for i , k := range keys {
538+ builder .WriteString (indent + " " )
539+ builder .WriteString ("[yellow]" )
540+ builder .WriteString (tview .Escape (k ))
541+ builder .WriteString ("[-]: " )
542+ colorizeJSON (builder , v [k ], level + 1 )
543+ if i < len (keys )- 1 {
544+ builder .WriteString ("," )
545+ }
546+ builder .WriteString ("\n " )
547+ }
548+ builder .WriteString (indent + "}" )
549+ case []interface {}:
550+ builder .WriteString ("[\n " )
551+ for i , item := range v {
552+ builder .WriteString (indent + " " )
553+ colorizeJSON (builder , item , level + 1 )
554+ if i < len (v )- 1 {
555+ builder .WriteString ("," )
556+ }
557+ builder .WriteString ("\n " )
558+ }
559+ builder .WriteString (indent + "]" )
560+ case string :
561+ builder .WriteString ("[green]\" " )
562+ builder .WriteString (tview .Escape (v ))
563+ builder .WriteString ("\" [-]" )
564+ case float64 , int , int64 , uint64 , float32 :
565+ builder .WriteString ("[cyan]" )
566+ builder .WriteString (fmt .Sprintf ("%v" , v ))
567+ builder .WriteString ("[-]" )
568+ case bool :
569+ builder .WriteString ("[magenta]" )
570+ builder .WriteString (fmt .Sprintf ("%v" , v ))
571+ builder .WriteString ("[-]" )
572+ case nil :
573+ builder .WriteString ("[gray]null[-]" )
574+ default :
575+ builder .WriteString (tview .Escape (fmt .Sprintf ("%v" , v )))
576+ }
577+ }
0 commit comments