diff --git a/api/api.go b/api/api.go index 9efd4ab..876f456 100644 --- a/api/api.go +++ b/api/api.go @@ -21,6 +21,7 @@ type Store interface { Search(filter string) ([]model.Event, error) CachedStats(model.StatsRange) model.Stats Events() <-chan model.Event + TxInfo(id string) (model.TxInfo, error) } type API struct { @@ -42,6 +43,7 @@ func New(s Store, b *Broadcaster) (*API, error) { } a.r.HandleFunc("GET /", a.index) + a.r.HandleFunc("GET /tx/{tx}", a.txPage) a.r.HandleFunc("GET /api/v1/stream", a.stream) a.r.HandleFunc("GET /api/v1/stats", a.stats) a.r.HandleFunc("GET /api/v1/events", a.table) @@ -69,6 +71,37 @@ func (a *API) index(w http.ResponseWriter, r *http.Request) { } } +func (a *API) txPage(w http.ResponseWriter, r *http.Request) { + tx := r.PathValue("tx") + txInfo, err := a.s.TxInfo(tx) + if err != nil { + http.Error(w, "tx not found", http.StatusNotFound) + return + } + + if r.Header.Get("Hx-Request") == "true" { + w.Header().Set("HX-Push-Url", r.URL.EscapedPath()) + if err := templates.Tx(txInfo).Render(r.Context(), w); err != nil { + slog.Error("failed to render TxInfo", slog.Any("err", err)) + } + return + } + + // nolint:errcheck + if pusher, ok := w.(http.Pusher); ok { + pusher.Push("/assets/style.css", nil) + pusher.Push("/assets/htmx.min.js", nil) + pusher.Push("/assets/sse.js", nil) + pusher.Push("/assets/Inter-Regular.ttf", nil) + pusher.Push("/assets/Inter-Bold.ttf", nil) + pusher.Push("/assets/Inter-SemiBold.ttf", nil) + } + + if err := templates.TxPage(txInfo).Render(r.Context(), w); err != nil { + slog.Error("failed to render TxPage", slog.Any("err", err)) + } +} + func (a *API) stats(w http.ResponseWriter, r *http.Request) { sr, err := model.ParseStatsRange(r.URL.Query().Get("range")) if err != nil { diff --git a/api/assets/style.css b/api/assets/style.css index d4c3696..e31d0e4 100644 --- a/api/assets/style.css +++ b/api/assets/style.css @@ -624,13 +624,173 @@ input:checked+.slider:before { background: #b556ff; } + +#tx-container { + display: flex; + flex-direction: column; + margin-bottom: auto; +} + +#tx-info { + display: flex; + flex-direction: column; + margin-bottom: auto; +} + +#tx-top, +#tx-bottom { + display: flex; + flex-direction: row; + width: 50%; +} + +#tx-top { + margin-right: 5px; +} + +#tx-bottom { + margin-left: 5px; +} + +.tx-info-header { + font-weight: 600; + font-size: 13px; + line-height: 16px; + margin: 10px; +} + +#tx-info-blocks { + display: flex; + flex-direction: row; +} + +#tx-state-block { + display: flex; + flex-direction: column; + width: 50%; + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0px; + margin-right: 5px; +} + +#tx-state-block>div { + display: flex; + background: #EEEEEE; + border-radius: 100px; + flex-grow: 1; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 20px; + line-height: 24px; +} + +#tx-current-state { + margin-bottom: 10px; +} + +.tx-id-block { + display: flex; + flex-direction: column; + border: 1px solid #EEEEEE; + border-radius: 2px; + width: 50%; + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0px; +} + +.tx-id-block:nth-child(1) { + margin-right: 5px; +} + +.tx-id-block:nth-child(2) { + margin-left: 5px; +} + +.tx-id-block-wrap { + padding: 20px; + display: flex; + flex-direction: column; +} + +.tx-id-block-value { + display: flex; + word-break: break-all; + font-weight: 400; + font-size: 15px; + line-height: 18px; + width: 100%; + margin-bottom: 40px; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; +} + +.tx-id-block-bottom { + display: flex; + flex-direction: row; + margin-top: auto; +} + +.tx-id-block-header { + display: flex; + font-weight: 700; + font-size: 20px; + line-height: 24px; + margin-right: auto; +} + +.tx-id-block-copy { + display: flex; + align-items: center; + padding: 0px 5px; + background: #EEEEEE; + border-radius: 40px; + max-height: 20px; + cursor: pointer; + width: fit-content; +} + +.tx-id-block-copy svg { + width: 20px; + height: 20px; +} + +#tx-log { + display: none; +} + +.tx-log-events {} + +.tx-log-row {} + +.tx-log-state {} + +.tx-log-id {} + +.tx-log-timestamp {} + +body.dark .tx-id-block-copy, +body.dark .tx-id-block-cop, +body.dark #tx-state-block>div { + background-color: #333333; +} + +body.dark .tx-id-block-copy svg path { + fill: #FFFFFF +} + @media (max-width: 1500px) { .number-title { word-spacing: 1000px; } } -@media (max-width: 1200px) { +@media (max-width: 1246px) { #stats { height: 246px; } @@ -652,6 +812,19 @@ input:checked+.slider:before { order: 1; margin-right: auto; } + + .tx-id-block-bottom { + display: flex; + flex-direction: column-reverse; + } + + .tx-id-block-copy { + margin-top: 38px; + } + + .tx-id-block-header { + margin-top: 10px; + } } @media (max-width: 800px) { @@ -759,6 +932,26 @@ input:checked+.slider:before { min-width: 0px; } + .tx-id-block-value { + margin-bottom: 10px; + } + + #tx-info-blocks { + flex-direction: column; + } + + #tx-top { + width: 100%; + margin-bottom: 5px; + } + + #tx-bottom { + width: 100%; + margin-top: 5px; + margin-left: 0px; + } + + #footer { flex-direction: column; height: auto; diff --git a/api/broadcaster_test.go b/api/broadcaster_test.go index d5d46db..5dc1ef9 100644 --- a/api/broadcaster_test.go +++ b/api/broadcaster_test.go @@ -181,8 +181,10 @@ type MockStore struct { searchResult []model.Event searchErr error events chan model.Event + txInfo model.TxInfo } +func (m *MockStore) TxInfo(string) (model.TxInfo, error) { return m.txInfo, nil } func (m *MockStore) CachedStats(model.StatsRange) model.Stats { return m.stats } func (m *MockStore) Events() <-chan model.Event { return m.events } func (m *MockStore) Search(string) ([]model.Event, error) { return m.searchResult, m.searchErr } diff --git a/api/templates/index.templ b/api/templates/index.templ index fdfbeab..c442010 100644 --- a/api/templates/index.templ +++ b/api/templates/index.templ @@ -14,7 +14,7 @@ templ Index() { @head() - +
@header() @Stats(model.Stats{}) @@ -25,6 +25,21 @@ templ Index() { } +templ TxPage(tx model.TxInfo) { + + + @head() + +
+ @header() + @Stats(model.Stats{}) + @Tx(tx) + @footer() +
+ + +} + templ Stats(stats model.Stats) {
@@ -84,7 +99,7 @@ templ Table(events []model.Event, query url.Values) { } templ Row(e model.Event) { -
+
State
@@ -114,6 +129,78 @@ templ Row(e model.Event) {
+
+ +
+
+} + +templ Tx(tx model.TxInfo) { +
+ @TxInfo(tx) + @TxLog(tx) +
+} + +templ TxInfo(tx model.TxInfo) { +
+
Transaction Info
+
+
+
+
{ tx.State }
+
{ formatDuration(tx.Duration) }
+
+ @TxIDBlock(tx.TxID, "Transaction ID") +
+
+ @TxIDBlock(tx.UserID, "User ID") + @TxIDBlock(tx.ProverID, "Prover ID") +
+
+
+} + +templ TxIDBlock(id, header string) { +
+
+
{ id }
+
+
{ header }
+
+ Copy + + + +
+
+
+
+} + +templ TxLog(tx model.TxInfo) { +
+
Log
+
+ for _, e := range tx.Log { + @TxLogEvent(e) + } +
+
+} + +templ TxLogEvent(e model.TxLogEvent) { +
+
+ { e.State } +
+
+ { e.IDType } + { e.ID } +
+
+ { e.Timestamp.Format("03:04 PM, 02/01/06") } +
} @@ -141,7 +228,7 @@ templ head() { Devnet Explorer - + @@ -210,6 +297,10 @@ templ footer() {
} +script copyToClipboard(text string) { + navigator.clipboard.writeText(text) +} + const suffixes = " kMGTPEZYRQ" func format(i uint64) string { @@ -225,3 +316,10 @@ func formatPercentage(f float64) string { } return fmt.Sprintf("%s%.2f%s", sign, f, "%") } + +func formatDuration(d time.Duration) string { + hour := int(d.Hours()) + minute := int(d.Minutes()) % 60 + second := int(d.Seconds()) % 60 + return fmt.Sprintf("%02dH:%02dM:%02dS", hour, minute, second) +} diff --git a/api/templates/index_templ.go b/api/templates/index_templ.go index 62c5d54..54c3005 100644 --- a/api/templates/index_templ.go +++ b/api/templates/index_templ.go @@ -41,7 +41,7 @@ func Index() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -72,7 +72,7 @@ func Index() templ.Component { }) } -func Stats(stats model.Stats) templ.Component { +func TxPage(tx model.TxInfo) templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -85,110 +85,162 @@ func Stats(stats model.Stats) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = head().Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.RegisteredUsers)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 31, Col: 85} + return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + templ_7745c5c3_Err = header().Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Registered Users
") + templ_7745c5c3_Err = Stats(model.Stats{}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Tx(tx).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = footer().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func Stats(stats model.Stats) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.RegisteredUsersDelta)) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.RegisteredUsers)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 34, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 46, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Registered Users
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.ProversDeployed)) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.RegisteredUsersDelta)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 38, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 49, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Provers Deployed
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.ProversDeployedDelta)) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.ProversDeployed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 41, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 53, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Provers Deployed
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.ProofsGenerated)) + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.ProversDeployedDelta)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 47, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 56, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Proofs Generated
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.ProofsGeneratedDelta)) + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.ProofsGenerated)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 50, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 62, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Proofs Generated
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.ProofsVerified)) + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.ProofsGeneratedDelta)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 54, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 65, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Proof Verifications
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.ProofsVerifiedDelta)) + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(format(stats.ProofsVerified)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 57, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 69, Col: 83} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Proof Verifications
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(formatPercentage(stats.ProofsVerifiedDelta)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 72, Col: 74} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -208,9 +260,9 @@ func Table(events []model.Event, query url.Values) templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var11 := templ.GetChildren(ctx) - if templ_7745c5c3_Var11 == nil { - templ_7745c5c3_Var11 = templ.NopComponent + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
State
Transaction ID
Prover ID
Time
State
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
State
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 = []any{"tag", e.State} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -276,7 +336,7 @@ func Row(e model.Event) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var13).String())) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var14).String())) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -284,12 +344,12 @@ func Row(e model.Event) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(e.State) + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(e.State) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 91, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 106, Col: 45} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -297,12 +357,12 @@ func Row(e model.Event) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(e.TxID) + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(e.TxID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 96, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 111, Col: 17} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -315,12 +375,12 @@ func Row(e model.Event) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(e.Tag) + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(e.Tag) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 104, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 119, Col: 41} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -333,12 +393,12 @@ func Row(e model.Event) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(e.ProverID) + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(e.ProverID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 106, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 121, Col: 23} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -346,16 +406,299 @@ func Row(e model.Event) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(e.Timestamp.Format("03:04 PM, 02/01/06")) + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(e.Timestamp.Format("03:04 PM, 02/01/06")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 112, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 127, Col: 70} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func Tx(tx model.TxInfo) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TxInfo(tx).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TxLog(tx).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func TxInfo(tx model.TxInfo) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var21 := templ.GetChildren(ctx) + if templ_7745c5c3_Var21 == nil { + templ_7745c5c3_Var21 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Transaction Info
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(tx.State) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 150, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(tx.Duration)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 151, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TxIDBlock(tx.TxID, "Transaction ID").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TxIDBlock(tx.UserID, "User ID").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TxIDBlock(tx.ProverID, "Prover ID").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func TxIDBlock(id, header string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var24 := templ.GetChildren(ctx) + if templ_7745c5c3_Var24 == nil { + templ_7745c5c3_Var24 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(id) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 166, Col: 38} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(header) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 168, Col: 44} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(id)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Copy
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func TxLog(tx model.TxInfo) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var28 := templ.GetChildren(ctx) + if templ_7745c5c3_Var28 == nil { + templ_7745c5c3_Var28 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Log
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, e := range tx.Log { + templ_7745c5c3_Err = TxLogEvent(e).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func TxLogEvent(e model.TxLogEvent) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var29 := templ.GetChildren(ctx) + if templ_7745c5c3_Var29 == nil { + templ_7745c5c3_Var29 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(e.State) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 194, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(e.IDType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 197, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(e.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 198, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(e.Timestamp.Format("03:04 PM, 02/01/06")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 201, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -374,12 +717,12 @@ func head() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var19 := templ.GetChildren(ctx) - if templ_7745c5c3_Var19 == nil { - templ_7745c5c3_Var19 = templ.NopComponent + templ_7745c5c3_Var34 := templ.GetChildren(ctx) + if templ_7745c5c3_Var34 == nil { + templ_7745c5c3_Var34 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Devnet Explorer") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Devnet Explorer") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -398,9 +741,9 @@ func header() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var20 := templ.GetChildren(ctx) - if templ_7745c5c3_Var20 == nil { - templ_7745c5c3_Var20 = templ.NopComponent + templ_7745c5c3_Var35 := templ.GetChildren(ctx) + if templ_7745c5c3_Var35 == nil { + templ_7745c5c3_Var35 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Gevulot
Live
Light
Dark
") @@ -422,21 +765,21 @@ func footer() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var21 := templ.GetChildren(ctx) - if templ_7745c5c3_Var21 == nil { - templ_7745c5c3_Var21 = templ.NopComponent + templ_7745c5c3_Var36 := templ.GetChildren(ctx) + if templ_7745c5c3_Var36 == nil { + templ_7745c5c3_Var36 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Copyright ©") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("2006")) + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("2006")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 200, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/index.templ`, Line: 287, Col: 61} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -451,6 +794,16 @@ func footer() templ.Component { }) } +func copyToClipboard(text string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_copyToClipboard_126b`, + Function: `function __templ_copyToClipboard_126b(text){navigator.clipboard.writeText(text) +}`, + Call: templ.SafeScript(`__templ_copyToClipboard_126b`, text), + CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_126b`, text), + } +} + const suffixes = " kMGTPEZYRQ" func format(i uint64) string { @@ -466,3 +819,10 @@ func formatPercentage(f float64) string { } return fmt.Sprintf("%s%.2f%s", sign, f, "%") } + +func formatDuration(d time.Duration) string { + hour := int(d.Hours()) + minute := int(d.Minutes()) % 60 + second := int(d.Seconds()) % 60 + return fmt.Sprintf("%02dH:%02dM:%02dS", hour, minute, second) +} diff --git a/app/app.go b/app/app.go index 168d03e..72eb5ba 100644 --- a/app/app.go +++ b/app/app.go @@ -20,6 +20,7 @@ type Store interface { Search(filter string) ([]model.Event, error) Stats(model.StatsRange) (model.Stats, error) Events() <-chan model.Event + TxInfo(id string) (model.TxInfo, error) Runnable } diff --git a/magefile.go b/magefile.go index feeae69..bd858ce 100644 --- a/magefile.go +++ b/magefile.go @@ -59,8 +59,8 @@ func (Go) Run() error { return sh.Run(buildOutput) } -// Runs devnet-explorer binary -func (Go) RunWithMockDB() error { +// Runs devnet-explorer in mock store mode +func (Go) RunDev() error { mg.SerialDeps(Go.Build) return sh.RunWith(map[string]string{"MOCK_STORE": "true"}, buildOutput) } diff --git a/model/model.go b/model/model.go index ca82485..8a3aac2 100644 --- a/model/model.go +++ b/model/model.go @@ -25,6 +25,22 @@ type Event struct { Timestamp time.Time `json:"timestamp"` } +type TxInfo struct { + State string `json:"state"` + Duration time.Duration `json:"duration"` + TxID string `json:"tx_id"` + UserID string `json:"user_id"` + ProverID string `json:"prover_id"` + Log []TxLogEvent `json:"log"` +} + +type TxLogEvent struct { + State string `json:"state"` + IDType string `json:"id_type"` + ID string `json:"id"` + Timestamp time.Time `json:"timestamp"` +} + type StatsRange interface { String() string sr() diff --git a/store/mock/store.go b/store/mock/store.go index 9add17d..292db3c 100644 --- a/store/mock/store.go +++ b/store/mock/store.go @@ -77,6 +77,50 @@ func (s *Store) Search(filter string) ([]model.Event, error) { return events, nil } +func (s *Store) TxInfo(id string) (model.TxInfo, error) { + now := time.Now() + proverID := sha512.Sum512([]byte(time.Now().String())) + verifierID := sha512.Sum512([]byte(time.Now().String())) + completeID := sha512.Sum512([]byte(time.Now().String())) + userID := sha512.Sum512([]byte(time.Now().String())) + info := model.TxInfo{ + State: "completed", + Duration: 65*time.Minute + 12*time.Second, + TxID: id, + UserID: hex.EncodeToString(userID[:]), + ProverID: hex.EncodeToString(proverID[:]), + } + + info.Log = []model.TxLogEvent{ + { + State: "completed", + IDType: "node id", + ID: hex.EncodeToString(completeID[:]), + Timestamp: now, + }, + { + State: "verifying", + IDType: "node id", + ID: hex.EncodeToString(verifierID[:]), + Timestamp: now.Add(-12 * time.Minute), + }, + { + State: "proving", + IDType: "node id", + ID: info.ProverID, + Timestamp: now.Add(-33 * time.Minute), + }, + { + State: "submitted", + IDType: "user id", + ID: info.UserID, + Timestamp: now.Add(-65 * time.Minute), + }, + } + + return info, nil +} + func (s *Store) Stop() error { close(s.done) return nil diff --git a/store/pg/store.go b/store/pg/store.go index 190dfd2..be81c9b 100644 --- a/store/pg/store.go +++ b/store/pg/store.go @@ -158,6 +158,26 @@ func (s *Store) Search(filter string) ([]model.Event, error) { return events, nil } +func (s *Store) TxInfo(id string) (model.TxInfo, error) { + // TODO: Query tx info fields + const infoQuery = `` + + info := model.TxInfo{} + if _, err := s.db.Select(&info, infoQuery, id); err != nil { + return model.TxInfo{}, err + } + + // TODO: Query tx log events + const logQuery = `` + + info.Log = []model.TxLogEvent{} + if _, err := s.db.Select(&info.Log, logQuery, id); err != nil { + return model.TxInfo{}, err + } + + return info, nil +} + func (s *Store) Events() <-chan model.Event { return s.events }