refactor(admin): use inline pixel heights for overview charts
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m40s

Refactors the admin overview dashboard charts to use inline pixel heights (up to 150px) instead of CSS variables and percentage-based heights. This provides more robust rendering and layout control.

Changes include:
- Replacing `Height` with `HeightPx` in chart bar structures.
- Rendering inline styles for height and width on charts and status bars.
- Adding fallback data attributes (`data-height-px`, `data-chart-value`, etc.) and loading a new fallback script (`25-admin-charts.js`).
- Updating and expanding test coverage to assert correct scaling and HTML rendering.
This commit is contained in:
2026-06-01 12:30:59 +03:00
parent 38afc6c34d
commit c9f865cd85
6 changed files with 174 additions and 45 deletions

View File

@@ -159,15 +159,17 @@ type adminOverview struct {
}
type adminChartBar struct {
Label string
Value string
Height int // 0-100, percent of the tallest bar
Label string
Value string
HeightPx int
RawValue int64
}
type adminStatBar struct {
Label string
Value string
Percent int
Label string
Value string
RawValue int
WidthPercent int
}
type adminBoxView struct {
@@ -336,6 +338,7 @@ func (a *App) recentBoxViews(boxes []services.AdminBox, limit int) []adminBoxVie
// status distributions for the overview dashboard.
func buildAdminOverview(boxes []services.AdminBox, stats services.AdminStats) adminOverview {
const days = 14
const chartMaxHeightPx = 150
now := time.Now().UTC()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
@@ -374,14 +377,16 @@ func buildAdminOverview(boxes []services.AdminBox, stats services.AdminStats) ad
storageDays := make([]adminChartBar, days)
for i := 0; i < days; i++ {
uploadDays[i] = adminChartBar{
Label: labels[i],
Value: strconv.Itoa(counts[i]),
Height: scaleHeight(int64(counts[i]), int64(maxCount)),
Label: labels[i],
Value: strconv.Itoa(counts[i]),
HeightPx: scaleHeightPx(int64(counts[i]), int64(maxCount), chartMaxHeightPx),
RawValue: int64(counts[i]),
}
storageDays[i] = adminChartBar{
Label: labels[i],
Value: helpers.FormatBytes(bytes[i]),
Height: scaleHeight(bytes[i], maxBytes),
Label: labels[i],
Value: helpers.FormatBytes(bytes[i]),
HeightPx: scaleHeightPx(bytes[i], maxBytes, chartMaxHeightPx),
RawValue: bytes[i],
}
}
@@ -389,10 +394,11 @@ func buildAdminOverview(boxes []services.AdminBox, stats services.AdminStats) ad
if activeBoxes < 0 {
activeBoxes = 0
}
maxStatusValue := maxInt(activeBoxes, stats.ExpiredBoxes, stats.ProtectedBoxes)
statusBars := []adminStatBar{
{Label: "Active", Value: strconv.Itoa(activeBoxes), Percent: percentOf(activeBoxes, stats.TotalBoxes)},
{Label: "Expired", Value: strconv.Itoa(stats.ExpiredBoxes), Percent: percentOf(stats.ExpiredBoxes, stats.TotalBoxes)},
{Label: "Password-protected", Value: strconv.Itoa(stats.ProtectedBoxes), Percent: percentOf(stats.ProtectedBoxes, stats.TotalBoxes)},
{Label: "Active", Value: strconv.Itoa(activeBoxes), RawValue: activeBoxes, WidthPercent: percentOf(activeBoxes, maxStatusValue)},
{Label: "Expired", Value: strconv.Itoa(stats.ExpiredBoxes), RawValue: stats.ExpiredBoxes, WidthPercent: percentOf(stats.ExpiredBoxes, maxStatusValue)},
{Label: "Password-protected", Value: strconv.Itoa(stats.ProtectedBoxes), RawValue: stats.ProtectedBoxes, WidthPercent: percentOf(stats.ProtectedBoxes, maxStatusValue)},
}
return adminOverview{
@@ -402,13 +408,16 @@ func buildAdminOverview(boxes []services.AdminBox, stats services.AdminStats) ad
}
}
func scaleHeight(value, max int64) int {
func scaleHeightPx(value, max int64, maxHeightPx int) int {
if max <= 0 || value <= 0 {
return 0
}
height := int(value * 100 / max)
if height < 4 {
height = 4
height := int(value * int64(maxHeightPx) / max)
if height < 8 {
height = 8
}
if height > maxHeightPx {
return maxHeightPx
}
return height
}
@@ -420,6 +429,16 @@ func percentOf(value, total int) int {
return value * 100 / total
}
func maxInt(values ...int) int {
max := 0
for _, value := range values {
if value > max {
max = value
}
}
return max
}
func (a *App) AdminUsers(w http.ResponseWriter, r *http.Request) {
if !a.requireAdmin(w, r) {
return