Add storage analytics page

This commit is contained in:
2026-05-18 01:09:34 +02:00
parent a91b9b36d3
commit 691041814f
9 changed files with 439 additions and 8 deletions

View File

@@ -4,9 +4,11 @@ import (
"io"
"os"
"path/filepath"
"sort"
"time"
"github.com/google/uuid"
"github.com/shirou/gopsutil/v3/disk"
)
type Service struct {
@@ -209,3 +211,66 @@ func (s *Service) buildPath(id, filename string) string {
func (s *Service) GetAllFiles() ([]FileRecord, error) {
return s.repo.GetAll()
}
func (s *Service) GetStorageStats() (*StorageStats, error) {
files, err := s.repo.GetAll()
if err != nil {
return nil, err
}
stats := &StorageStats{}
var total int64
var active, deleted int
for _, f := range files {
stats.TotalFiles++
if f.Deleted {
deleted++
} else {
active++
}
total += f.Size
}
stats.TotalBytes = total
stats.ActiveFiles = active
stats.DeletedFiles = deleted
if stats.TotalFiles > 0 {
stats.AverageFileSize = total / int64(stats.TotalFiles)
}
// Biggest files
sort.Slice(files, func(i, j int) bool {
return files[i].Size > files[j].Size
})
if len(files) > 10 {
stats.LargestFiles = files[:10]
} else {
stats.LargestFiles = files
}
usage, err := disk.Usage(s.storageDir)
if err == nil {
stats.DiskTotalBytes = int64(usage.Total)
stats.DiskFreeBytes = int64(usage.Free)
stats.DiskUsedBytes = int64(usage.Used)
}
// tmp chunk usage
var tmpTotal int64
filepath.Walk("tmp", func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
tmpTotal += info.Size()
}
return nil
})
stats.TempBytes = tmpTotal
return stats, nil
}

18
internal/file/stats.go Normal file
View File

@@ -0,0 +1,18 @@
package file
type StorageStats struct {
TotalFiles int
ActiveFiles int
DeletedFiles int
TotalBytes int64
AverageFileSize int64
DiskTotalBytes int64
DiskFreeBytes int64
DiskUsedBytes int64
TempBytes int64
LargestFiles []FileRecord
}

View File

@@ -91,7 +91,7 @@ func (h *Handler) AdminPage(c *gin.Context) {
page = 1
}
limit := 10
limit := 25
offset := (page - 1) * limit
files, totalCount, err := h.fileService.GetPaginatedFiles(limit, offset)
@@ -103,11 +103,6 @@ func (h *Handler) AdminPage(c *gin.Context) {
return
}
// Only check files on the current page.
// Status meanings:
// - green: file exists on disk
// - red: file missing
// - rainbow: stat error (something unexpected)
type AdminFileView struct {
file.FileRecord
ActualStatus string
@@ -153,4 +148,26 @@ func (h *Handler) Logout(c *gin.Context) {
func (h *Handler) ChangePasswordPage(c *gin.Context) {
c.HTML(200, "changePassword.html", nil)
}
}
func (h *Handler) AdminStatsPage(c *gin.Context) {
log := middleware.StructuredLog(c).With().
Str("event", "admin_stats_page").
Logger()
stats, err := h.fileService.GetStorageStats()
if err != nil {
log.Error().Err(err).Msg("Failed to load storage stats")
c.HTML(500, "admin_stats.html", gin.H{
"error": err.Error(),
})
return
}
log.Info().Msg("Admin stats page viewed")
c.HTML(200, "admin_stats.html", gin.H{
"Stats": stats,
})
}

View File

@@ -20,6 +20,7 @@ func RegisterRoutes(r *gin.Engine, h *Handler, userService *user.Service) {
adminRoutes.Use(user.ForcePasswordChangeMiddleware(userService))
adminRoutes.GET("/admin", h.AdminPage)
adminRoutes.GET("/admin/stats", h.AdminStatsPage)
adminRoutes.GET("/config", h.ConfigPage)
adminRoutes.POST("/config", h.ConfigSave)
adminRoutes.GET("/logout", h.Logout)