refactor: wire Dashboard, ListIssues, and ListPulls to use template files

Replace inline fmt.Sprintf HTML generation in Dashboard, ListIssues,
and ListPulls handlers with template.ParseFiles rendering of
dashboard.html, issues.html, and pulls.html respectively.

ListIssues now reads ?org= and ?state= query params to filter results.
ListPulls now reads ?org= query param to filter results.

Closes leeworks-agents/gitea-mobile#34

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
agent-company
2026-03-26 17:08:58 +00:00
parent 919a91d6aa
commit d433801da6
+109 -112
View File
@@ -180,158 +180,155 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
token := getToken(r) token := getToken(r)
orgs := h.getUserOrgs(r) orgs := h.getUserOrgs(r)
type dashboardData struct {
Items []giteaclient.TriageItem
Error string
}
var data dashboardData
if len(orgs) == 0 { if len(orgs) == 0 {
renderPage(w, r, "Dashboard", "dashboard", data.Error = "No organizations found. Check your token permissions."
`<h1>Dashboard</h1><p class="empty">No organizations found. Check your token permissions.</p>`) } else {
return queue, err := h.Client.GetTriageQueue(r.Context(), token, orgs)
if err != nil {
slog.Error("failed to get triage queue", "error", err)
data.Error = "Error loading triage queue."
} else {
data.Items = queue
}
} }
queue, err := h.Client.GetTriageQueue(r.Context(), token, orgs) tmpl, err := template.ParseFiles("internal/templates/dashboard.html")
if err != nil { if err != nil {
slog.Error("failed to get triage queue", "error", err) slog.Error("failed to parse dashboard template", "error", err)
renderPage(w, r, "Dashboard", "dashboard", http.Error(w, "template error", http.StatusInternalServerError)
`<h1>Dashboard</h1><p class="empty">Error loading triage queue.</p>`)
return return
} }
if len(queue) == 0 { var buf strings.Builder
renderPage(w, r, "Dashboard", "dashboard", if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil {
`<h1>Dashboard</h1><p class="empty">No items need attention. Nice work!</p>`) slog.Error("failed to execute dashboard template", "error", err)
http.Error(w, "template error", http.StatusInternalServerError)
return return
} }
content := `<h1>Dashboard</h1>` renderPage(w, r, "Dashboard", "dashboard", buf.String())
for _, item := range queue {
typeBadge := `<span class="type-badge type-issue">issue</span>`
if item.Type == "pull" {
typeBadge = `<span class="type-badge type-pull">PR</span>`
}
labels := ""
for _, l := range item.Labels {
color := "#8b949e"
switch l {
case "P1":
color = "#f85149"
case "P2":
color = "#d29922"
case "P3":
color = "#58a6ff"
}
labels += fmt.Sprintf(`<span class="label" style="color:%s;border:1px solid %s">%s</span>`, color, color, template.HTMLEscapeString(l))
}
content += fmt.Sprintf(`<div class="card">
<div class="card-title">%s %s</div>
<div class="card-meta">%s/%s #%d %s</div>
</div>`, typeBadge, template.HTMLEscapeString(item.Title),
template.HTMLEscapeString(item.RepoOwner),
template.HTMLEscapeString(item.RepoName),
item.Number, labels)
}
renderPage(w, r, "Dashboard", "dashboard", content)
} }
// ListIssues handles GET /issues. // ListIssues handles GET /issues.
func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) {
token := getToken(r) token := getToken(r)
orgs := h.getUserOrgs(r) orgNames := h.getUserOrgs(r)
if len(orgs) == 0 { type issuesData struct {
renderPage(w, r, "Issues", "issues", Issues []giteaclient.Issue
`<h1>Issues</h1><p class="empty">No organizations found.</p>`) Orgs []string
return SelectedOrg string
SelectedState string
HasMore bool
NextPage int
Error string
} }
issues, err := h.Client.ListAllIssues(r.Context(), token, orgs) selectedOrg := r.URL.Query().Get("org")
selectedState := r.URL.Query().Get("state")
if selectedState == "" {
selectedState = "open"
}
data := issuesData{
Orgs: orgNames,
SelectedOrg: selectedOrg,
SelectedState: selectedState,
}
if len(orgNames) == 0 {
data.Error = "No organizations found."
} else {
// Filter to selected org if specified.
queryOrgs := orgNames
if selectedOrg != "" {
queryOrgs = []string{selectedOrg}
}
issues, err := h.Client.ListAllIssues(r.Context(), token, queryOrgs)
if err != nil {
slog.Error("failed to list issues", "error", err)
data.Error = "Error loading issues."
} else {
data.Issues = issues
}
}
tmpl, err := template.ParseFiles("internal/templates/issues.html")
if err != nil { if err != nil {
slog.Error("failed to list issues", "error", err) slog.Error("failed to parse issues template", "error", err)
renderPage(w, r, "Issues", "issues", http.Error(w, "template error", http.StatusInternalServerError)
`<h1>Issues</h1><p class="empty">Error loading issues.</p>`)
return return
} }
if len(issues) == 0 { var buf strings.Builder
renderPage(w, r, "Issues", "issues", if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil {
`<h1>Issues</h1><p class="empty">No open issues found.</p>`) slog.Error("failed to execute issues template", "error", err)
http.Error(w, "template error", http.StatusInternalServerError)
return return
} }
content := `<h1>Issues</h1>` renderPage(w, r, "Issues", "issues", buf.String())
for _, issue := range issues {
labels := ""
for _, l := range issue.Labels {
labels += fmt.Sprintf(`<span class="label" style="color:#%s;border:1px solid #%s">%s</span>`,
l.Color, l.Color, template.HTMLEscapeString(l.Name))
}
assignee := ""
if issue.Assignee != nil {
assignee = fmt.Sprintf(` &middot; %s`, template.HTMLEscapeString(issue.Assignee.Login))
}
content += fmt.Sprintf(`<div class="card">
<div class="card-title">%s</div>
<div class="card-meta">%s/%s #%d %s%s</div>
</div>`, template.HTMLEscapeString(issue.Title),
template.HTMLEscapeString(issue.RepoOwner),
template.HTMLEscapeString(issue.RepoName),
issue.Number, labels, assignee)
}
renderPage(w, r, "Issues", "issues", content)
} }
// ListPulls handles GET /pulls. // ListPulls handles GET /pulls.
func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) {
token := getToken(r) token := getToken(r)
orgs := h.getUserOrgs(r) orgNames := h.getUserOrgs(r)
if len(orgs) == 0 { type pullsData struct {
renderPage(w, r, "Pull Requests", "pulls", Pulls []giteaclient.PullRequest
`<h1>Pull Requests</h1><p class="empty">No organizations found.</p>`) Orgs []string
return SelectedOrg string
Error string
} }
prs, err := h.Client.ListAllPullRequests(r.Context(), token, orgs) selectedOrg := r.URL.Query().Get("org")
data := pullsData{
Orgs: orgNames,
SelectedOrg: selectedOrg,
}
if len(orgNames) == 0 {
data.Error = "No organizations found."
} else {
queryOrgs := orgNames
if selectedOrg != "" {
queryOrgs = []string{selectedOrg}
}
prs, err := h.Client.ListAllPullRequests(r.Context(), token, queryOrgs)
if err != nil {
slog.Error("failed to list pull requests", "error", err)
data.Error = "Error loading pull requests."
} else {
data.Pulls = prs
}
}
tmpl, err := template.ParseFiles("internal/templates/pulls.html")
if err != nil { if err != nil {
slog.Error("failed to list pull requests", "error", err) slog.Error("failed to parse pulls template", "error", err)
renderPage(w, r, "Pull Requests", "pulls", http.Error(w, "template error", http.StatusInternalServerError)
`<h1>Pull Requests</h1><p class="empty">Error loading pull requests.</p>`)
return return
} }
if len(prs) == 0 { var buf strings.Builder
renderPage(w, r, "Pull Requests", "pulls", if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil {
`<h1>Pull Requests</h1><p class="empty">No open pull requests found.</p>`) slog.Error("failed to execute pulls template", "error", err)
http.Error(w, "template error", http.StatusInternalServerError)
return return
} }
content := `<h1>Pull Requests</h1>` renderPage(w, r, "Pull Requests", "pulls", buf.String())
for _, pr := range prs {
labels := ""
for _, l := range pr.Labels {
labels += fmt.Sprintf(`<span class="label" style="color:#%s;border:1px solid #%s">%s</span>`,
l.Color, l.Color, template.HTMLEscapeString(l.Name))
}
stats := fmt.Sprintf(`<span style="color:#3fb950">+%d</span> <span style="color:#f85149">-%d</span>`, pr.Additions, pr.Deletions)
mergeStatus := ""
if pr.Mergeable {
mergeStatus = `<span style="color:#3fb950;font-size:0.7rem;">mergeable</span>`
}
content += fmt.Sprintf(`<div class="card">
<div class="card-title"><span class="type-badge type-pull">PR</span> %s</div>
<div class="card-meta">%s/%s #%d %s %s %s</div>
</div>`, template.HTMLEscapeString(pr.Title),
template.HTMLEscapeString(pr.RepoOwner),
template.HTMLEscapeString(pr.RepoName),
pr.Number, labels, stats, mergeStatus)
}
renderPage(w, r, "Pull Requests", "pulls", content)
} }
// IssueDetail handles GET /issues/{owner}/{repo}/{index}. // IssueDetail handles GET /issues/{owner}/{repo}/{index}.