A vibe coded tangled fork which supports pijul.
1package knotmirror
2
3import (
4 "database/sql"
5 "embed"
6 "html/template"
7 "log/slog"
8 "net/http"
9 "strconv"
10 "time"
11
12 "github.com/go-chi/chi/v5"
13 "tangled.org/core/appview/pagination"
14 "tangled.org/core/knotmirror/db"
15 "tangled.org/core/knotmirror/models"
16)
17
18//go:embed templates/*.html
19var templateFS embed.FS
20
21const repoPageSize = 20
22
23type AdminServer struct {
24 db *sql.DB
25}
26
27func NewAdminServer(database *sql.DB) *AdminServer {
28 return &AdminServer{db: database}
29}
30
31func (s *AdminServer) Router() http.Handler {
32 r := chi.NewRouter()
33 r.Get("/repos", s.handleRepos())
34 r.Get("/hosts", s.handleHosts())
35 return r
36}
37
38func funcmap() template.FuncMap {
39 return template.FuncMap{
40 "add": func(a, b int) int { return a + b },
41 "sub": func(a, b int) int { return a - b },
42 "readt": func(ts int64) string {
43 if ts == 0 {
44 return "n/a"
45 }
46 return time.Unix(ts, 0).Format("2006-01-02 15:04")
47 },
48 "const": func() map[string]any {
49 return map[string]any{
50 "AllRepoStates": models.AllRepoStates,
51 "AllHostStatuses": models.AllHostStatuses,
52 }
53 },
54 }
55}
56
57func (s *AdminServer) handleRepos() http.HandlerFunc {
58 tpl := template.Must(template.New("").Funcs(funcmap()).ParseFS(templateFS, "templates/base.html", "templates/repos.html"))
59 return func(w http.ResponseWriter, r *http.Request) {
60 pageNum, _ := strconv.Atoi(r.URL.Query().Get("page"))
61 if pageNum < 1 {
62 pageNum = 1
63 }
64 page := pagination.Page{
65 Offset: (pageNum - 1) * repoPageSize,
66 Limit: repoPageSize,
67 }
68
69 var (
70 did = r.URL.Query().Get("did")
71 knot = r.URL.Query().Get("knot")
72 state = r.URL.Query().Get("state")
73 )
74
75 repos, err := db.ListRepos(r.Context(), s.db, page, did, knot, state)
76 if err != nil {
77 http.Error(w, err.Error(), http.StatusInternalServerError)
78 }
79 counts, err := db.GetRepoCountsByState(r.Context(), s.db)
80 if err != nil {
81 http.Error(w, err.Error(), http.StatusInternalServerError)
82 }
83 err = tpl.ExecuteTemplate(w, "base", map[string]any{
84 "Repos": repos,
85 "RepoCounts": counts,
86 "Page": pageNum,
87 "FilterByDid": did,
88 "FilterByKnot": knot,
89 "FilterByState": models.RepoState(state),
90 })
91 if err != nil {
92 slog.Error("failed to render", "err", err)
93 }
94 }
95}
96
97func (s *AdminServer) handleHosts() http.HandlerFunc {
98 tpl := template.Must(template.New("").Funcs(funcmap()).ParseFS(templateFS, "templates/base.html", "templates/hosts.html"))
99 return func(w http.ResponseWriter, r *http.Request) {
100 var status = r.URL.Query().Get("status")
101
102 hosts, err := db.ListHosts(r.Context(), s.db, models.HostStatus(status))
103 if err != nil {
104 http.Error(w, err.Error(), http.StatusInternalServerError)
105 }
106 err = tpl.ExecuteTemplate(w, "base", map[string]any{
107 "Hosts": hosts,
108 "FilterByStatus": models.HostStatus(status),
109 })
110 if err != nil {
111 slog.Error("failed to render", "err", err)
112 }
113 }
114}