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:
+109
-112
@@ -182,158 +182,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(` · %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}.
|
||||||
|
|||||||
Reference in New Issue
Block a user