A vibe coded tangled fork which supports pijul.
at dc655d5f5b789e40b71b1976a4883f196462c5ad 114 lines 2.9 kB view raw
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}