Files
owenandClaude Sonnet 5 ba1f7c4c03 Initial vibeproxy implementation
Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go,
with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS
framework), and a REST API under /api/v1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
2026-08-17 10:28:59 -05:00

100 lines
2.1 KiB
Go

package web
import (
"fmt"
"html/template"
"net/http"
"strings"
"time"
)
var funcMap = template.FuncMap{
"join": func(items []string, sep string) string { return strings.Join(items, sep) },
"fmtTime": func(t time.Time) string {
if t.IsZero() {
return "-"
}
return t.Format("2006-01-02 15:04:05")
},
"timeAgo": timeAgo,
"deref": func(t *time.Time) time.Time {
if t == nil {
return time.Time{}
}
return *t
},
"deref64": func(v *int64) int64 {
if v == nil {
return 0
}
return *v
},
"slice": func(items ...int) []int { return items },
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"mul": func(a, b int) int { return a * b },
"iterate": func(n int) []int {
out := make([]int, n)
for i := range out {
out[i] = i
}
return out
},
"statusClass": func(status int) string {
switch {
case status >= 500:
return "text-danger"
case status >= 400:
return "text-warning"
case status >= 300:
return "text-info"
default:
return "text-success"
}
},
}
func timeAgo(t time.Time) string {
if t.IsZero() {
return "never"
}
d := time.Since(t)
switch {
case d < time.Minute:
return "just now"
case d < time.Hour:
return fmt.Sprintf("%dm ago", int(d.Minutes()))
case d < 24*time.Hour:
return fmt.Sprintf("%dh ago", int(d.Hours()))
default:
return fmt.Sprintf("%dd ago", int(d.Hours()/24))
}
}
var templates *template.Template
func init() {
t, err := template.New("").Funcs(funcMap).ParseFS(templatesFS, "templates/*.html", "templates/partials/*.html")
if err != nil {
panic(err)
}
templates = t
}
// PageData is the common envelope passed to every page template.
type PageData struct {
Active string // nav item key for sidebar highlighting
Title string
CSRFToken string
Flash string
FlashKind string // success|danger|warning
Data any
}
func render(w http.ResponseWriter, name string, data PageData) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := templates.ExecuteTemplate(w, name, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}