306 lines
7.3 KiB
Go
306 lines
7.3 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)
|
|
}
|