85 строки
2.1 KiB
Go
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
|
|
}
|