check / check (push) Canceled after 0s
Takes .golangci.yml, .dockerignore, .gitignore, .editorconfig, the workflow and REPO_POLICIES.md from sneak/prompts 0653579, with this repository's own entries after the shared content. Lint (golangci-lint v2.14.0) and test are Dockerfile phases that script/lint and script/test build alone with a new CACHEBUST value, keeping Go's and golangci-lint's caches; the image build depends on both. make fmt and make fmt-check also format the markdown with prettier, pinned in yarn.lock. The agent guidance moves to AGENTS.md. The Go code is fixed for what the new configuration finds, and the AS and prefix detail reads now return the rows' error after their loops. Model: opus-5-5
193 lines
5.0 KiB
Go
193 lines
5.0 KiB
Go
// Package templates provides embedded HTML templates for the RouteWatch application
|
|
package templates
|
|
|
|
import (
|
|
_ "embed"
|
|
"html/template"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/version"
|
|
)
|
|
|
|
//go:embed index.html
|
|
var indexHTML string
|
|
|
|
//go:embed status.html
|
|
var statusHTML string
|
|
|
|
//go:embed as_detail.html
|
|
var asDetailHTML string
|
|
|
|
//go:embed prefix_detail.html
|
|
var prefixDetailHTML string
|
|
|
|
//go:embed prefix_length.html
|
|
var prefixLengthHTML string
|
|
|
|
// Templates contains all parsed templates
|
|
type Templates struct {
|
|
// Index is the template for the home page
|
|
Index *template.Template
|
|
// Status is the template for the main status page
|
|
Status *template.Template
|
|
// ASDetail is the template for displaying AS (Autonomous System) details
|
|
ASDetail *template.Template
|
|
// PrefixDetail is the template for displaying prefix details
|
|
PrefixDetail *template.Template
|
|
// PrefixLength is the template for displaying prefixes by length
|
|
PrefixLength *template.Template
|
|
}
|
|
|
|
var (
|
|
//nolint:gochecknoglobals // Singleton pattern for templates
|
|
defaultTemplates *Templates
|
|
//nolint:gochecknoglobals // Singleton pattern for templates
|
|
once sync.Once
|
|
)
|
|
|
|
const (
|
|
hoursPerDay = 24
|
|
daysPerMonth = 30
|
|
cidrPartCount = 2 // A CIDR has two parts: prefix and length
|
|
)
|
|
|
|
// timeSince returns a human-readable duration since the given time
|
|
func timeSince(t time.Time) string {
|
|
duration := time.Since(t)
|
|
if duration < time.Minute {
|
|
return "just now"
|
|
}
|
|
|
|
if duration < time.Hour {
|
|
minutes := int(duration.Minutes())
|
|
if minutes == 1 {
|
|
return "1 minute ago"
|
|
}
|
|
|
|
return duration.Truncate(time.Minute).String() + " ago"
|
|
}
|
|
|
|
if duration < hoursPerDay*time.Hour {
|
|
hours := int(duration.Hours())
|
|
if hours == 1 {
|
|
return "1 hour ago"
|
|
}
|
|
|
|
return duration.Truncate(time.Hour).String() + " ago"
|
|
}
|
|
|
|
days := int(duration.Hours() / hoursPerDay)
|
|
if days == 1 {
|
|
return "1 day ago"
|
|
}
|
|
|
|
if days < daysPerMonth {
|
|
return duration.Truncate(hoursPerDay*time.Hour).String() + " ago"
|
|
}
|
|
|
|
return t.Format("2006-01-02")
|
|
}
|
|
|
|
// prefixURL generates a URL path for a prefix in CIDR notation.
|
|
// Takes a prefix like "192.168.1.0/24" and returns "/prefix/192.168.1.0/24"
|
|
// with the prefix part URL-encoded to handle IPv6 colons.
|
|
func prefixURL(cidr string) string {
|
|
// Split CIDR into prefix and length
|
|
parts := strings.SplitN(cidr, "/", cidrPartCount)
|
|
if len(parts) != cidrPartCount {
|
|
// Fallback if no slash found
|
|
return "/prefix/" + url.PathEscape(cidr) + "/0"
|
|
}
|
|
|
|
return "/prefix/" + url.PathEscape(parts[0]) + "/" + parts[1]
|
|
}
|
|
|
|
// initTemplates parses all embedded templates
|
|
func initTemplates() {
|
|
var err error
|
|
|
|
defaultTemplates = &Templates{}
|
|
|
|
// Create common template functions
|
|
funcs := template.FuncMap{
|
|
"timeSince": timeSince,
|
|
"urlEncode": url.QueryEscape,
|
|
"prefixURL": prefixURL,
|
|
"appName": func() string { return version.Name },
|
|
"appAuthor": func() string { return version.Author },
|
|
"appAuthorURL": func() string { return version.AuthorURL },
|
|
"appLicense": func() string { return version.License },
|
|
"appRepoURL": func() string { return version.RepoURL },
|
|
"appGitRevision": func() string { return version.GitRevisionShort },
|
|
"appGitCommitURL": version.CommitURL,
|
|
}
|
|
|
|
// Parse index template
|
|
defaultTemplates.Index, err = template.New("index").Funcs(funcs).Parse(indexHTML)
|
|
if err != nil {
|
|
panic("failed to parse index template: " + err.Error())
|
|
}
|
|
|
|
// Parse status template
|
|
defaultTemplates.Status, err = template.New("status").Funcs(funcs).Parse(statusHTML)
|
|
if err != nil {
|
|
panic("failed to parse status template: " + err.Error())
|
|
}
|
|
|
|
// Parse AS detail template
|
|
defaultTemplates.ASDetail, err = template.New("asDetail").
|
|
Funcs(funcs).Parse(asDetailHTML)
|
|
if err != nil {
|
|
panic("failed to parse AS detail template: " + err.Error())
|
|
}
|
|
|
|
// Parse prefix detail template
|
|
defaultTemplates.PrefixDetail, err = template.New("prefixDetail").
|
|
Funcs(funcs).Parse(prefixDetailHTML)
|
|
if err != nil {
|
|
panic("failed to parse prefix detail template: " + err.Error())
|
|
}
|
|
|
|
// Parse prefix length template
|
|
defaultTemplates.PrefixLength, err = template.New("prefixLength").
|
|
Funcs(funcs).Parse(prefixLengthHTML)
|
|
if err != nil {
|
|
panic("failed to parse prefix length template: " + err.Error())
|
|
}
|
|
}
|
|
|
|
// Get returns the singleton Templates instance
|
|
func Get() *Templates {
|
|
once.Do(initTemplates)
|
|
|
|
return defaultTemplates
|
|
}
|
|
|
|
// IndexTemplate returns the parsed index template
|
|
func IndexTemplate() *template.Template {
|
|
return Get().Index
|
|
}
|
|
|
|
// StatusTemplate returns the parsed status template
|
|
func StatusTemplate() *template.Template {
|
|
return Get().Status
|
|
}
|
|
|
|
// ASDetailTemplate returns the parsed AS detail template
|
|
func ASDetailTemplate() *template.Template {
|
|
return Get().ASDetail
|
|
}
|
|
|
|
// PrefixDetailTemplate returns the parsed prefix detail template
|
|
func PrefixDetailTemplate() *template.Template {
|
|
return Get().PrefixDetail
|
|
}
|
|
|
|
// PrefixLengthTemplate returns the parsed prefix length template
|
|
func PrefixLengthTemplate() *template.Template {
|
|
return Get().PrefixLength
|
|
}
|