summaryrefslogtreecommitdiffstats
path: root/web/utils.go
blob: 18b5b52e5599d6daadf434d860e3f922788d457e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package web

import (
	"fmt"
	"html/template"
	"net/url"
	"strings"
	"time"
)

func templateFuncs() template.FuncMap {
	return template.FuncMap{
		"repoURL":     repoURL,
		"manifestURL": manifestURL,
		"pull":        pullAddress,
		"size":        formatSize,
		"timestamp":   timestamp,
		"age":         displayAge,
		"join":        strings.Join,
	}
}

// sanitizeURLPath escapes each path segment separately, preserving slashes in
// names such as group/repository. Appends a trailing slash.
func sanitizeURLPath(path string) string {
	parts := strings.Split(path, "/")
	for i := range parts {
		parts[i] = url.PathEscape(parts[i])
	}
	return strings.Join(parts, "/") + "/"
}

func repoURL(name string) string {
	return "/r/" + sanitizeURLPath(name)
}

func manifestURL(repository, digest, tag string) string {
	path := repoURL(repository) + "m/" + sanitizeURLPath(digest)
	if tag != "" {
		path += "?" + url.Values{"tag": {tag}}.Encode()
	}
	return path
}

func pullAddress(host, repository, tag, digest string) string {
	address := strings.TrimSuffix(host, "/") + "/" + repository
	if tag != "" {
		address += ":" + tag
	}
	if digest != "" {
		address += "@" + digest
	}
	return address
}

func formatSize(value int64) string {
	if value >= 1024*1024 {
		return fmt.Sprintf("%.1fMB", float64(value)/(1024*1024))
	}
	if value >= 1024 {
		return fmt.Sprintf("%.1fKB", float64(value)/1024)
	}
	return fmt.Sprintf("%dB", value)
}

func timestamp(t time.Time) string {
	if t.IsZero() {
		return ""
	}
	return t.Format(time.DateTime + " MST")
}

func displayAge(updated time.Time, frozen string) string {
	if frozen != "" {
		return frozen
	}
	if updated.IsZero() {
		return "unknown"
	}
	elapsed := time.Since(updated)
	if elapsed < time.Minute {
		return "just now"
	}
	for _, unit := range []struct {
		duration time.Duration
		label    string
	}{
		{365 * 24 * time.Hour, "year"},
		{30 * 24 * time.Hour, "month"},
		{24 * time.Hour, "day"},
		{time.Hour, "hour"},
		{time.Minute, "minute"},
	} {
		if count := int(elapsed / unit.duration); count > 0 {
			suffix := "s"
			if count == 1 {
				suffix = ""
			}
			return fmt.Sprintf("%d %s%s ago", count, unit.label, suffix)
		}
	}
	return "just now"
}