oups
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/leolionad58/ticketbot/internal/db"
|
||||
panelauth "github.com/leolionad58/ticketbot/internal/panel/auth"
|
||||
)
|
||||
|
||||
// BotStatus holds live bot state, populated by the BotService.
|
||||
type BotStatus struct {
|
||||
Online bool
|
||||
StartedAt time.Time
|
||||
LatencyMs int64
|
||||
GuildName string
|
||||
}
|
||||
|
||||
func (s BotStatus) Uptime() string {
|
||||
if !s.Online {
|
||||
return "—"
|
||||
}
|
||||
d := time.Since(s.StartedAt).Truncate(time.Second)
|
||||
h := int(d.Hours())
|
||||
m := int(d.Minutes()) % 60
|
||||
sec := int(d.Seconds()) % 60
|
||||
if h > 0 {
|
||||
return fmt.Sprintf("%dh %dm", h, m)
|
||||
}
|
||||
return fmt.Sprintf("%dm %ds", m, sec)
|
||||
}
|
||||
|
||||
// DashboardStats holds pre-computed stats for the dashboard.
|
||||
type DashboardStats struct {
|
||||
OpenTickets int
|
||||
ClosedLast30 int
|
||||
AvgClaimMinutes int
|
||||
AvgResolutionMinutes int
|
||||
}
|
||||
|
||||
// ChartBar is one bar in the daily chart.
|
||||
type ChartBar struct {
|
||||
Day string
|
||||
Count int
|
||||
}
|
||||
|
||||
// DashboardHandler serves the main dashboard page.
|
||||
type DashboardHandler struct {
|
||||
TicketRepo *db.TicketRepo
|
||||
PanelRepo *db.PanelConfigRepo
|
||||
GetBotStatus func() BotStatus
|
||||
Renderer *Renderer
|
||||
}
|
||||
|
||||
func (h *DashboardHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
base := h.Renderer.base(r, "dashboard")
|
||||
|
||||
sess := panelauth.SessionFromContext(ctx)
|
||||
if sess != nil {
|
||||
base.CSRFToken = sess.CSRFToken
|
||||
}
|
||||
|
||||
stats := h.loadStats(ctx)
|
||||
chart := h.loadChart(ctx)
|
||||
staff := h.loadStaff(ctx)
|
||||
panels, _ := h.PanelRepo.List(ctx)
|
||||
|
||||
status := BotStatus{}
|
||||
if h.GetBotStatus != nil {
|
||||
status = h.GetBotStatus()
|
||||
}
|
||||
|
||||
h.Renderer.Page(w, "dashboard", struct {
|
||||
baseData
|
||||
Stats DashboardStats
|
||||
Chart []ChartBar
|
||||
ChartMax int
|
||||
Staff []db.StaffStat
|
||||
Panels []*db.PanelConfig
|
||||
BotOnline bool
|
||||
Uptime string
|
||||
LatencyMs int64
|
||||
GuildName string
|
||||
}{
|
||||
baseData: base,
|
||||
Stats: stats,
|
||||
Chart: chart,
|
||||
ChartMax: chartMax(chart),
|
||||
Staff: staff,
|
||||
Panels: panels,
|
||||
BotOnline: status.Online,
|
||||
Uptime: status.Uptime(),
|
||||
LatencyMs: status.LatencyMs,
|
||||
GuildName: status.GuildName,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DashboardHandler) loadStats(ctx context.Context) DashboardStats {
|
||||
var stats DashboardStats
|
||||
if h.TicketRepo == nil {
|
||||
return stats
|
||||
}
|
||||
open, _ := h.TicketRepo.CountByStatus(ctx, "open")
|
||||
claimed, _ := h.TicketRepo.CountByStatus(ctx, "claimed")
|
||||
stats.OpenTickets = open + claimed
|
||||
|
||||
since := time.Now().AddDate(0, 0, -30)
|
||||
stats.ClosedLast30, _ = h.TicketRepo.CountClosedSince(ctx, since)
|
||||
stats.AvgClaimMinutes, _ = h.TicketRepo.AvgClaimMinutes(ctx, since)
|
||||
stats.AvgResolutionMinutes, _ = h.TicketRepo.AvgResolutionMinutes(ctx, since)
|
||||
return stats
|
||||
}
|
||||
|
||||
func (h *DashboardHandler) loadChart(ctx context.Context) []ChartBar {
|
||||
if h.TicketRepo == nil {
|
||||
return nil
|
||||
}
|
||||
daily, _ := h.TicketRepo.DailyTicketCounts(ctx, 30)
|
||||
dayMap := make(map[string]int, len(daily))
|
||||
for _, d := range daily {
|
||||
dayMap[d.Day] = d.Count
|
||||
}
|
||||
bars := make([]ChartBar, 30)
|
||||
for i := range bars {
|
||||
t := time.Now().AddDate(0, 0, -29+i)
|
||||
day := t.Format("2006-01-02")
|
||||
bars[i] = ChartBar{Day: t.Format("02/01"), Count: dayMap[day]}
|
||||
}
|
||||
return bars
|
||||
}
|
||||
|
||||
func (h *DashboardHandler) loadStaff(ctx context.Context) []db.StaffStat {
|
||||
if h.TicketRepo == nil {
|
||||
return nil
|
||||
}
|
||||
stats, _ := h.TicketRepo.StaffStats(ctx, 30)
|
||||
return stats
|
||||
}
|
||||
|
||||
func chartMax(bars []ChartBar) int {
|
||||
m := 1
|
||||
for _, b := range bars {
|
||||
if b.Count > m {
|
||||
m = b.Count
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func fmtMinutes(m int) string {
|
||||
if m <= 0 {
|
||||
return "—"
|
||||
}
|
||||
if m < 60 {
|
||||
return fmt.Sprintf("%dm", m)
|
||||
}
|
||||
return fmt.Sprintf("%dh%dm", m/60, m%60)
|
||||
}
|
||||
Reference in New Issue
Block a user