diff --git a/internal/handler/views/remote_nodes.templ b/internal/handler/views/remote_nodes.templ index e93e1cd..9461056 100644 --- a/internal/handler/views/remote_nodes.templ +++ b/internal/handler/views/remote_nodes.templ @@ -329,10 +329,10 @@ templ TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagi { fmt.Sprintf("%d", row.ProberID) } if row.Status == 1 { OK - { fmt.Sprintf("%d", row.Height) } + { fmt.Sprintf("%d", row.Height) } { time.Unix(row.AdjustedTime, 0).UTC().Format("Jan 2, 2006 15:04 MST") } - { fmt.Sprintf("%d", row.DatabaseSize) } - { fmt.Sprintf("%d", row.Difficulty) } + { utils.FormatBytes(row.DatabaseSize, 0) } + { utils.FormatHashes(float64(row.Difficulty)) } { fmt.Sprintf("%d", row.EstimateFee) } } else { ERR diff --git a/internal/handler/views/remote_nodes_templ.go b/internal/handler/views/remote_nodes_templ.go index 4bba9a3..ec048b8 100644 --- a/internal/handler/views/remote_nodes_templ.go +++ b/internal/handler/views/remote_nodes_templ.go @@ -865,14 +865,14 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin return templ_7745c5c3_Err } if row.Status == 1 { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("OK") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("OK") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var35 string templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.Height)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 332, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 332, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) if templ_7745c5c3_Err != nil { @@ -896,9 +896,9 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin return templ_7745c5c3_Err } var templ_7745c5c3_Var37 string - templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.DatabaseSize)) + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatBytes(row.DatabaseSize, 0)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 334, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 334, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { @@ -909,9 +909,9 @@ func TableLogs(hxPath string, data monero.FetchLogs, q monero.QueryLogs, p pagin return templ_7745c5c3_Err } var templ_7745c5c3_Var38 string - templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", row.Difficulty)) + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatHashes(float64(row.Difficulty))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 335, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/remote_nodes.templ`, Line: 335, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { diff --git a/utils/human_readable.go b/utils/human_readable.go index 2c739a3..10c1898 100644 --- a/utils/human_readable.go +++ b/utils/human_readable.go @@ -2,15 +2,11 @@ package utils import ( "fmt" + "math" "strconv" "time" ) -// Convert the float to a string, trimming unnecessary zeros -func FormatFloat(f float64) string { - return strconv.FormatFloat(f, 'f', -1, 64) -} - // TimeSince converts an int64 timestamp to a relative time string func TimeSince(timestamp int64) string { var duration time.Duration @@ -45,3 +41,68 @@ func TimeSince(timestamp int64) string { return fmt.Sprintf("%d months %s", months, suffix) } } + +// Convert the float to a string, trimming unnecessary zeros +func FormatFloat(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} + +// Formats bytes as a human-readable string with the specified number of decimal places. +func FormatBytes(bytes, decimals int) string { + if bytes == 0 { + return "0 Bytes" + } + + const k float64 = 1024 + sizes := []string{"Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} + + i := int(math.Floor(math.Log(float64(bytes)) / math.Log(k))) + dm := decimals + if dm < 0 { + dm = 0 + } + + value := float64(bytes) / math.Pow(k, float64(i)) + return fmt.Sprintf("%.*f %s", dm, value, sizes[i]) +} + +// Formats a hash value (h) into human readable format. +// +// This function was adapted from jtgrassie/monero-pool project. +// Source: https://github.com/jtgrassie/monero-pool/blob/master/src/webui-embed.html +// +// Copyright (c) 2018, The Monero Project +func FormatHashes(h float64) string { + switch { + case h < 1e-12: + return "0 H" + case h < 1e-9: + return fmt.Sprintf("%.0f pH", maxPrecision(h*1e12, 0)) + case h < 1e-6: + return fmt.Sprintf("%.0f nH", maxPrecision(h*1e9, 0)) + case h < 1e-3: + return fmt.Sprintf("%.0f μH", maxPrecision(h*1e6, 0)) + case h < 1: + return fmt.Sprintf("%.0f mH", maxPrecision(h*1e3, 0)) + case h < 1e3: + return fmt.Sprintf("%.0f H", h) + case h < 1e6: + return fmt.Sprintf("%.2f KH", maxPrecision(h*1e-3, 2)) + case h < 1e9: + return fmt.Sprintf("%.2f MH", maxPrecision(h*1e-6, 2)) + default: + return fmt.Sprintf("%.2f GH", maxPrecision(h*1e-9, 2)) + } +} + +// Returns a number with a maximum precision. +// +// This function was adapted from jtgrassie/monero-pool project. +// Source: https://github.com/jtgrassie/monero-pool/blob/master/src/webui-embed.html +// +// Copyright (c) 2018, The Monero Project +func maxPrecision(n float64, p int) float64 { + format := "%." + strconv.Itoa(p) + "f" + result, _ := strconv.ParseFloat(fmt.Sprintf(format, n), 64) + return result +}