Files
worker/internal/webapp/template_helpers.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

85 строки
2.1 KiB
Go

package webapp
import (
"fmt"
"strings"
"time"
)
// Template helpers. Kept in their own file so templates.go stays
// focused on parsing/wiring.
// fmtBytes renders a byte count as a human-readable string. The
// unit is picked automatically (B / KiB / MiB / GiB / TiB).
func fmtBytes(n uint64) string {
const k = 1024
if n < k {
return fmt.Sprintf("%d B", n)
}
div, exp := uint64(k), 1
for n2 := n / k; n2 >= k; n2 /= k {
div *= k
exp++
}
suffix := []string{"KiB", "MiB", "GiB", "TiB", "PiB"}[exp-1]
return fmt.Sprintf("%.2f %s", float64(n)/float64(div), suffix)
}
// fmtPercent renders a 0..100 number as "N.NN%".
func fmtPercent(p float64) string {
return fmt.Sprintf("%.2f%%", p)
}
// fmtDuration renders a duration as "1d 2h 3m 4s". The format is
// stable across short and long uptimes.
func fmtDuration(d time.Duration) string {
if d < 0 {
d = 0
}
days := int(d / (24 * time.Hour))
d -= time.Duration(days) * 24 * time.Hour
hours := int(d / time.Hour)
d -= time.Duration(hours) * time.Hour
minutes := int(d / time.Minute)
d -= time.Duration(minutes) * time.Minute
seconds := int(d / time.Second)
switch {
case days > 0:
return fmt.Sprintf("%dd %dh %dm %ds", days, hours, minutes, seconds)
case hours > 0:
return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds)
case minutes > 0:
return fmt.Sprintf("%dm %ds", minutes, seconds)
default:
return fmt.Sprintf("%ds", seconds)
}
}
// fmtTime renders a timestamp as "2006-01-02 15:04:05 UTC".
func fmtTime(t time.Time) string {
if t.IsZero() {
return "—"
}
return t.UTC().Format("2006-01-02 15:04:05 UTC")
}
// fmtBool renders a bool as "yes" / "no".
func fmtBool(b bool) string {
if b {
return "yes"
}
return "no"
}
// sanitizeName strips characters that could break out of an HTML
// attribute when interpolated by a template. Template auto-escapes
// by default, but a paranoid layer here keeps audit targets safe
// when they are rendered into other contexts.
func sanitizeName(s string) string {
s = strings.ReplaceAll(s, "\x00", "")
if len(s) > 128 {
s = s[:128]
}
return s
}