Files
routewatch/internal/templates/templates.go
T
clawbot 281d162e27
check / check (push) Canceled after 0s
Show figures of the routing table on the home page (closes #154)
The home page gets cards for the newest and oldest kept route, the newest
and oldest updated AS, the AS with the most prefixes, the ASes with a
single prefix and fifteen well-known networks. Two more cards count the
bogon announcements and the prefixes with several origin ASes. They link
to the new pages /bogons and /multiple-origins, which list up to 1,000
entries each.

The new package internal/bogon tells reserved prefixes and AS numbers,
from the IANA special-purpose and address space registries. Table.Overview
walks the table and lets the read lock go every 10,000 prefixes. The
server works the cards out once a minute and serves that copy. A new index
on asns(last_seen) finds the newest and oldest updated AS.

Model: opus-5-5
2026-10-27 18:27:01 +01:00

186 lines
5.3 KiB
Go

// Package templates provides embedded HTML templates for the RouteWatch application
package templates
import (
_ "embed"
"html/template"
"net/url"
"strings"
"sync"
"git.eeqj.de/sneak/routewatch/internal/version"
"github.com/dustin/go-humanize"
)
//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
//go:embed bogons.html
var bogonsHTML string
//go:embed multiple_origins.html
var multipleOriginsHTML 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
// Bogons is the template of the page that lists the bogon announcements
Bogons *template.Template
// MultipleOrigins is the template of the page that lists the prefixes
// announced by more than one origin AS
MultipleOrigins *template.Template
}
var (
//nolint:gochecknoglobals // Singleton pattern for templates
defaultTemplates *Templates
//nolint:gochecknoglobals // Singleton pattern for templates
once sync.Once
)
const cidrPartCount = 2 // A CIDR has two parts: prefix and length
// 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": humanize.Time,
"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())
}
defaultTemplates.Bogons, err = template.New("bogons").
Funcs(funcs).Parse(bogonsHTML)
if err != nil {
panic("failed to parse bogons template: " + err.Error())
}
defaultTemplates.MultipleOrigins, err = template.New("multipleOrigins").
Funcs(funcs).Parse(multipleOriginsHTML)
if err != nil {
panic("failed to parse multiple origins 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
}
// BogonsTemplate returns the parsed template of the bogon announcements page
func BogonsTemplate() *template.Template {
return Get().Bogons
}
// MultipleOriginsTemplate returns the parsed template of the page of prefixes
// announced by more than one origin AS
func MultipleOriginsTemplate() *template.Template {
return Get().MultipleOrigins
}