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 */