github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/status/handle_show.go (about)

     1  package statusPkg
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strings"
     7  	"text/template"
     8  	"time"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/output"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/tslib"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/version"
    18  )
    19  
    20  func (opts *StatusOptions) HandleShow(rCtx *output.RenderCtx) error {
    21  	if len(opts.Modes) > 0 {
    22  		return opts.HandleModes(rCtx)
    23  	}
    24  
    25  	testMode := opts.Globals.TestMode
    26  
    27  	fetchData := func(modelChan chan types.Modeler, errorChan chan error) {
    28  		s, err := opts.GetStatus(opts.Diagnose)
    29  		if err != nil {
    30  			errorChan <- err
    31  			return
    32  		}
    33  
    34  		// We want to short circuit the output in the non-json case
    35  		if toTemplate(s, opts.Globals.Writer, testMode, opts.Diagnose, logger.LogTimerOn(), opts.Globals.Format) {
    36  			return
    37  		}
    38  
    39  		modelChan <- s
    40  	}
    41  
    42  	return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOpts())
    43  }
    44  
    45  func ToProgress(chain string, diagnose bool, meta *types.MetaData) string {
    46  	nTs, _ := tslib.NTimestamps(chain) // when the file has one record, the block is zero, etc.
    47  	if nTs > 0 {
    48  		nTs--
    49  	}
    50  	format := "%d, %d, %d, %d ts: %d"
    51  	return fmt.Sprintf(format, meta.Latest, meta.Finalized, meta.Staging, meta.Unripe, nTs)
    52  }
    53  
    54  func (opts *StatusOptions) GetStatus(diagnose bool) (*types.Status, error) {
    55  	chain := opts.Globals.Chain
    56  	testMode := opts.Globals.TestMode
    57  
    58  	meta, err := opts.Conn.GetMetaData(false)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	diffs := &types.MetaData{
    63  		Latest:    0,
    64  		Finalized: meta.Latest - meta.Finalized,
    65  		Staging:   meta.Latest - meta.Staging,
    66  		Unripe:    meta.Latest - meta.Unripe,
    67  		Ripe:      meta.Latest - meta.Ripe,
    68  	}
    69  
    70  	vers, err := opts.Conn.GetClientVersion()
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	_, isTracing := opts.Conn.IsNodeTracing()
    76  	provider := config.GetChain(chain).RpcProvider
    77  	s := &types.Status{
    78  		ClientVersion: vers,
    79  		Version:       version.LibraryVersion,
    80  		RpcProvider:   provider,
    81  		RootConfig:    config.PathToRootConfig(),
    82  		ChainConfig:   config.MustGetPathToChainConfig(chain),
    83  		CachePath:     config.PathToCache(chain),
    84  		IndexPath:     config.PathToIndex(chain),
    85  		Progress:      ToProgress(chain, diagnose, meta),
    86  		IsTesting:     testMode,
    87  		IsApi:         opts.Globals.IsApiMode(),
    88  		IsArchive:     opts.Conn.IsNodeArchive(),
    89  		IsTracing:     isTracing,
    90  		HasEsKey:      validate.HasArticulationKey(true),
    91  		HasPinKey:     len(config.GetKey("pinata").ApiKey) > 0 || len(config.GetKey("pinata").Secret) > 0,
    92  		Chain:         chain,
    93  		NetworkId:     fmt.Sprint(meta.NetworkId),
    94  		ChainId:       fmt.Sprint(meta.ChainId),
    95  		Meta:          meta,
    96  		Diffs:         diffs,
    97  		// Finalized: meta.Latest - meta.Finalized,
    98  		// Staging:   meta.Latest - meta.Staging,
    99  		// Unripe:    meta.Latest - meta.Unripe,
   100  		// Ripe:      meta.Latest - meta.Ripe,
   101  	}
   102  
   103  	if testMode {
   104  		s.ClientVersion = "Client version"
   105  		s.Version = "GHC-TrueBlocks//vers-beta--git-hash---git-ts-"
   106  		s.RpcProvider = "--providers--"
   107  		s.RootConfig = "--paths--"
   108  		s.ChainConfig = "--paths--"
   109  		s.CachePath = "--paths--"
   110  		s.IndexPath = "--paths--"
   111  		s.Progress = "--client--, --final--, --staging--, --unripe-- ts: --ts--"
   112  		s.HasPinKey = false // the test machine doesn't have a key
   113  	}
   114  
   115  	return s, nil
   116  }
   117  
   118  func toTemplate(s *types.Status, w io.Writer, testMode, diagnose, logTimerOn bool, format string) bool {
   119  	if format == "json" {
   120  		return false
   121  	}
   122  
   123  	var timeDatePart string
   124  	if logTimerOn {
   125  		now := time.Now()
   126  		timeDatePart = now.Format("02-01|15:04:05.000 ")
   127  	} else {
   128  		timeDatePart = "INFO[DATE|TIME] "
   129  	}
   130  
   131  	table := templateStr
   132  	table = strings.Replace(table, "[CLIENT]", getClientTemplate(), -1)
   133  	table = strings.Replace(table, "[VERSION]", getVersionTemplate(), -1)
   134  	table = strings.Replace(table, "[IDS]", getIdTemplate(), -1)
   135  	table = strings.Replace(table, "[PROGRESS]", getProgress(s, testMode, diagnose), -1)
   136  	table = strings.Replace(table, "INFO ", timeDatePart+colors.Green, -1)
   137  	table = strings.Replace(table, "[RED]", colors.Red, -1)
   138  	table = strings.Replace(table, "[GREEN]", colors.Green, -1)
   139  	table = strings.Replace(table, "[OFF]", colors.Off, -1)
   140  	table = strings.Replace(table, ":", ":"+colors.Off, -1)
   141  
   142  	t, err := template.New("status").Parse(table)
   143  	if err != nil {
   144  		logger.Fatal("should not happen ==> bad template.", err)
   145  		return false
   146  	}
   147  
   148  	_ = t.Execute(w, s)
   149  	return true
   150  }
   151  
   152  func getClientTemplate() string {
   153  	archive := "{{if .IsArchive}}[GREEN]archive[OFF]{{else}}[RED]no archive[OFF]{{end}}"
   154  	testing := "{{if .IsTesting}}[GREEN]testing[OFF], {{end}}"
   155  	tracing := "{{if .IsTracing}}[GREEN]tracing[OFF]{{else}}[RED]no tracing[OFF]{{end}}"
   156  	return "{{.ClientVersion}} (" + archive + ", " + testing + tracing + ")"
   157  }
   158  
   159  func getVersionTemplate() string {
   160  	esKey := "{{if .HasEsKey}}[GREEN]eskey[OFF]{{else}}[RED]no eskey[OFF]{{end}}"
   161  	pinKey := "{{if .HasPinKey}}[GREEN]pinkey[OFF]{{else}}[RED]no pinkey[OFF]{{end}}"
   162  	return "{{.Version}} (" + esKey + ", " + pinKey + ")"
   163  }
   164  
   165  func getIdTemplate() string {
   166  	networkId := "{{if eq .NetworkId \"0\"}}[RED]{{.NetworkId}}[OFF]{{else}}[GREEN]{{.NetworkId}}[OFF]{{end}}"
   167  	chainId := "{{if eq .ChainId \"0\"}}[RED]{{.ChainId}}[OFF]{{else}}[GREEN]{{.ChainId}}[OFF]{{end}}"
   168  	return networkId + "/" + chainId
   169  }
   170  
   171  func getProgress(s *types.Status, testMode, diagnose bool) string {
   172  	if diagnose {
   173  		if testMode {
   174  			return "--diagnostics--"
   175  		}
   176  		nTs, _ := tslib.NTimestamps(s.Meta.Chain) // when the file has one record, the block is zero, etc.
   177  		if nTs > 0 {
   178  			nTs--
   179  		}
   180  		nTsDiff := s.Meta.Latest - nTs
   181  		if nTs > s.Meta.Latest {
   182  			nTsDiff = 0
   183  		}
   184  		ret := `
   185  INFO [OFF]  Chain Head [GREEN]{{.Meta.Latest}}[OFF]
   186  INFO [OFF]  Finalized  [GREEN]{{.Meta.Finalized}}[OFF] ([GREEN]{{.Diffs.Finalized}}[OFF] behind head)
   187  INFO [OFF]  Stage      [GREEN]{{.Meta.Staging}}[OFF] ([GREEN]{{.Diffs.Staging}}[OFF] behind head)
   188  INFO [OFF]  Indexing   [GREEN]{{.Meta.Unripe}}[OFF] ([GREEN]{{.Diffs.Unripe}}[OFF] behind head)
   189  INFO [OFF]  Timestamps [GREEN]{nTs}[OFF] ([GREEN]{nTsDiff}[OFF] behind head)
   190  `
   191  		ret = strings.Replace(ret, "{nTs}", fmt.Sprint(nTs), -1)
   192  		ret = strings.Replace(ret, "{nTsDiff}", fmt.Sprint(nTsDiff), -1)
   193  		return ret
   194  	}
   195  
   196  	return "          {{.Progress}}"
   197  }
   198  
   199  const templateStr = `INFO Client:            [CLIENT]
   200  INFO TrueBlocks:        [VERSION]
   201  INFO RPC Provider:      {{.RpcProvider}} - {{.Chain}} ([IDS])
   202  INFO Root Config Path:  {{.RootConfig}}
   203  INFO Chain Config Path: {{.ChainConfig}}
   204  INFO Cache Path:        {{.CachePath}}
   205  INFO Index Path:        {{.IndexPath}}
   206  INFO Progress:[PROGRESS]
   207  `
   208  
   209  /*
   210  TODO: Better diagnostics (see #3209)
   211  TODO: When Node synced, successfull chifra init but scraper not running
   212  TODO:
   213  TODO: WARN: Index is behind the chain head, but scraper is not running.
   214  TODO: WARN: This means chifra is not aware of latest transactions and can return incomplete data.
   215  TODO: WARN: Run `chifra daemon --scrape index` to solve the issue.
   216  TODO:
   217  TODO: Node is not syncing
   218  TODO:
   219  TODO: WARN: Your node seems to be syncing. During this state it can refuse queries from chifra
   220  TODO: WARN: or return incomplete data. If you observe such issues, please wait until your node
   221  TODO: WARN: is fully synced and your index catches up to the chain head.
   222  */