ui(relTime): tick relative timestamps client-side so long-open tabs don't freeze
CI / Test (rest) (pull_request) Successful in 9s
CI / Test (store) (pull_request) Successful in 6s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/amd64) (pull_request) Successful in 7s
CI / Lint (pull_request) Successful in 19s
CI / Build (linux/arm64) (pull_request) Successful in 7s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m26s
CI / Test (server-http) (pull_request) Successful in 2m34s
CI / Test (rest) (pull_request) Successful in 9s
CI / Test (store) (pull_request) Successful in 6s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/amd64) (pull_request) Successful in 7s
CI / Lint (pull_request) Successful in 19s
CI / Build (linux/arm64) (pull_request) Successful in 7s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m26s
CI / Test (server-http) (pull_request) Successful in 2m34s
formatRelTime now wraps its label in <time data-rel-ts=...>, and both layouts include a small ticker that re-renders every 30s. Without this, a job-detail page rendered an hour ago kept showing '2h ago' when the wall-clock truth was '3h ago'.
This commit is contained in:
@@ -221,23 +221,40 @@ func formatBytes(n int64) template.HTML {
|
||||
// "in 5m"-style. Accepts *time.Time or time.Time so templates can
|
||||
// pass either without fighting Go's lack of an address-of operator.
|
||||
// Anything else returns "—".
|
||||
func formatRelTime(v any) string {
|
||||
//
|
||||
// The output is wrapped in a <time data-rel-ts="..."> element so a
|
||||
// small client-side ticker (see base.html) can refresh the label
|
||||
// without a full page reload — otherwise a long-open tab shows
|
||||
// timestamps frozen at render time.
|
||||
func formatRelTime(v any) template.HTML {
|
||||
var t time.Time
|
||||
switch x := v.(type) {
|
||||
case time.Time:
|
||||
t = x
|
||||
case *time.Time:
|
||||
if x == nil {
|
||||
return "—"
|
||||
return template.HTML("—")
|
||||
}
|
||||
t = *x
|
||||
default:
|
||||
return "—"
|
||||
return template.HTML("—")
|
||||
}
|
||||
if t.IsZero() {
|
||||
return "—"
|
||||
return template.HTML("—")
|
||||
}
|
||||
d := time.Since(t)
|
||||
label := relTimeLabel(time.Since(t))
|
||||
return template.HTML(fmt.Sprintf(
|
||||
`<time data-rel-ts="%s" title="%s">%s</time>`,
|
||||
t.UTC().Format(time.RFC3339Nano),
|
||||
t.UTC().Format("2006-01-02 15:04:05 UTC"),
|
||||
label,
|
||||
))
|
||||
}
|
||||
|
||||
// relTimeLabel turns a duration-since-now into the short human label
|
||||
// used by formatRelTime (and mirrored verbatim by the JS ticker, so
|
||||
// keep the two in sync if you change the buckets).
|
||||
func relTimeLabel(d time.Duration) string {
|
||||
suffix := "ago"
|
||||
if d < 0 {
|
||||
d = -d
|
||||
|
||||
Reference in New Issue
Block a user