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
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user