- icons/gen writes to the current directory so `go generate ./internal/master/web/icons` works (output verified byte-identical) - share the registry-host check as imageref.IsRegistryHost (with test) - agents fragment refreshes the "N machines" header count out of band - ignore the saved machine filter at <=760px where the machine list is hidden - aria-current on the active machine filter link, aria-pressed on theme toggle - CLAUDE.md: icons package, SSE ping event, swarm_cluster_id Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
50 lines
1.2 KiB
Go
50 lines
1.2 KiB
Go
// Package imageref splits container image references into repository, tag
|
|
// and digest.
|
|
package imageref
|
|
|
|
import "strings"
|
|
|
|
// Ref is an image reference split into its parts. Tag and Digest are empty
|
|
// when the reference has none.
|
|
type Ref struct {
|
|
Repo string
|
|
Tag string
|
|
Digest string
|
|
}
|
|
|
|
// Parse splits ref into repository, tag and digest. A colon before the last
|
|
// "/" belongs to a registry host and port ("localhost:5000/foo"), not a tag.
|
|
func Parse(ref string) Ref {
|
|
var r Ref
|
|
if at := strings.Index(ref, "@"); at >= 0 {
|
|
r.Digest = ref[at+1:]
|
|
ref = ref[:at]
|
|
}
|
|
slash := strings.LastIndex(ref, "/")
|
|
last := ref[slash+1:]
|
|
if colon := strings.Index(last, ":"); colon >= 0 {
|
|
r.Tag = last[colon+1:]
|
|
ref = ref[:slash+1+colon]
|
|
}
|
|
r.Repo = ref
|
|
return r
|
|
}
|
|
|
|
// IsRegistryHost reports whether the first path segment of a repository names
|
|
// a registry: it contains a "." or ":" or is "localhost".
|
|
func IsRegistryHost(segment string) bool {
|
|
return strings.ContainsAny(segment, ".:") || segment == "localhost"
|
|
}
|
|
|
|
// String reassembles the reference as repo[:tag][@digest].
|
|
func (r Ref) String() string {
|
|
s := r.Repo
|
|
if r.Tag != "" {
|
|
s += ":" + r.Tag
|
|
}
|
|
if r.Digest != "" {
|
|
s += "@" + r.Digest
|
|
}
|
|
return s
|
|
}
|