cairn/internal/web/web.go

306 lines
7.2 KiB
Go

package web
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/mattnite/cairn/internal/models"
"github.com/mattnite/cairn/internal/regression"
)
type PageHandler struct {
Pool *pgxpool.Pool
Templates *Templates
}
type PageData struct {
Title string
Content any
}
func (h *PageHandler) Index(c *gin.Context) {
ctx := c.Request.Context()
artifacts, total, err := models.ListArtifacts(ctx, h.Pool, models.ListArtifactsParams{
Limit: 10,
})
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
repos, err := models.ListRepositories(ctx, h.Pool)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
var totalCG, openCG int
h.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM crash_groups").Scan(&totalCG)
h.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM crash_groups WHERE status = 'open'").Scan(&openCG)
// Top crashers
type topCrasher struct {
CrashGroupID string
Title string
OccurrenceCount int
RepoName string
}
var topCrashers []topCrasher
rows, err := h.Pool.Query(ctx, `
SELECT cg.id, cg.title, cs.occurrence_count, r.name
FROM crash_groups cg
JOIN crash_signatures cs ON cs.id = cg.crash_signature_id
JOIN repositories r ON r.id = cg.repository_id
WHERE cg.status = 'open'
ORDER BY cs.occurrence_count DESC
LIMIT 5
`)
if err == nil {
defer rows.Close()
for rows.Next() {
var tc topCrasher
if rows.Scan(&tc.CrashGroupID, &tc.Title, &tc.OccurrenceCount, &tc.RepoName) == nil {
topCrashers = append(topCrashers, tc)
}
}
}
data := PageData{
Title: "Dashboard",
Content: map[string]any{
"Artifacts": artifacts,
"TotalArtifacts": total,
"Repositories": repos,
"TotalCrashGroups": totalCG,
"OpenCrashGroups": openCG,
"TopCrashers": topCrashers,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "index", data)
}
func (h *PageHandler) Artifacts(c *gin.Context) {
limit, _ := strconv.Atoi(c.Query("limit"))
offset, _ := strconv.Atoi(c.Query("offset"))
if limit <= 0 {
limit = 50
}
artifacts, total, err := models.ListArtifacts(c.Request.Context(), h.Pool, models.ListArtifactsParams{
RepositoryID: c.Query("repository_id"),
Type: c.Query("type"),
Limit: limit,
Offset: offset,
})
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
data := PageData{
Title: "Artifacts",
Content: map[string]any{
"Artifacts": artifacts,
"Total": total,
"Limit": limit,
"Offset": offset,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "artifacts", data)
}
func (h *PageHandler) ArtifactDetail(c *gin.Context) {
id := c.Param("id")
artifact, err := models.GetArtifact(c.Request.Context(), h.Pool, id)
if err != nil {
c.String(http.StatusNotFound, "artifact not found")
return
}
data := PageData{
Title: "Artifact " + artifact.ID[:8],
Content: artifact,
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "artifact_detail", data)
}
func (h *PageHandler) Repos(c *gin.Context) {
repos, err := models.ListRepositories(c.Request.Context(), h.Pool)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
data := PageData{
Title: "Repositories",
Content: map[string]any{
"Repositories": repos,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "repos", data)
}
func (h *PageHandler) CrashGroups(c *gin.Context) {
limit, _ := strconv.Atoi(c.Query("limit"))
offset, _ := strconv.Atoi(c.Query("offset"))
if limit <= 0 {
limit = 50
}
groups, total, err := models.ListCrashGroups(
c.Request.Context(), h.Pool,
c.Query("repository_id"), c.Query("status"),
limit, offset,
)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
data := PageData{
Title: "Crash Groups",
Content: map[string]any{
"CrashGroups": groups,
"Total": total,
"Limit": limit,
"Offset": offset,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "crashgroups", data)
}
func (h *PageHandler) CrashGroupDetail(c *gin.Context) {
id := c.Param("id")
group, err := models.GetCrashGroup(c.Request.Context(), h.Pool, id)
if err != nil {
c.String(http.StatusNotFound, "crash group not found")
return
}
// Get artifacts linked to this crash group's signature.
artifacts, _, _ := models.ListArtifacts(c.Request.Context(), h.Pool, models.ListArtifactsParams{
SignatureID: group.CrashSignatureID,
Limit: 50,
})
data := PageData{
Title: "Crash Group: " + group.Title,
Content: map[string]any{
"Group": group,
"Artifacts": artifacts,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "crashgroup_detail", data)
}
func (h *PageHandler) Search(c *gin.Context) {
q := c.Query("q")
var artifacts []models.Artifact
var total int
if q != "" {
artifacts, total, _ = models.SearchArtifacts(c.Request.Context(), h.Pool, q, 50, 0)
}
data := PageData{
Title: "Search",
Content: map[string]any{
"Query": q,
"Artifacts": artifacts,
"Total": total,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "search", data)
}
func (h *PageHandler) Regression(c *gin.Context) {
repo := c.Query("repo")
base := c.Query("base")
head := c.Query("head")
content := map[string]any{
"Repo": repo,
"Base": base,
"Head": head,
}
if repo != "" && base != "" && head != "" {
r, err := models.GetRepositoryByName(c.Request.Context(), h.Pool, repo)
if err == nil {
result, err := regression.Compare(c.Request.Context(), h.Pool, r.ID, base, head)
if err == nil {
result.RepoName = repo
content["Result"] = result
}
}
}
data := PageData{
Title: "Regression Check",
Content: content,
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "regression", data)
}
func (h *PageHandler) Campaigns(c *gin.Context) {
limit, _ := strconv.Atoi(c.Query("limit"))
offset, _ := strconv.Atoi(c.Query("offset"))
if limit <= 0 {
limit = 50
}
campaigns, total, err := models.ListCampaigns(c.Request.Context(), h.Pool, c.Query("repository_id"), limit, offset)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
data := PageData{
Title: "Campaigns",
Content: map[string]any{
"Campaigns": campaigns,
"Total": total,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "campaigns", data)
}
func (h *PageHandler) CampaignDetail(c *gin.Context) {
id := c.Param("id")
campaign, err := models.GetCampaign(c.Request.Context(), h.Pool, id)
if err != nil {
c.String(http.StatusNotFound, "campaign not found")
return
}
artifacts, _, _ := models.ListArtifacts(c.Request.Context(), h.Pool, models.ListArtifactsParams{
CampaignID: campaign.ID,
Limit: 50,
})
data := PageData{
Title: "Campaign: " + campaign.Name,
Content: map[string]any{
"Campaign": campaign,
"Artifacts": artifacts,
},
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.Templates.Render(c.Writer, "campaign_detail", data)
}