v0.4.5: Add dedicated Backup page (Biztonsági mentés)
New /backups page with full backup system visibility: - Status overview cards (local/remote backup, DB count, repo size) - Schedule section with next-run times and retention policy - Database table with type, size, validation (table count), status - Snapshot history table with per-snapshot stats - Repository info card with paths, integrity status, remote placeholder - "Mentés most" button with auto-refresh polling - Empty state when backup not configured Backend: SnapshotRecord history (ring buffer), DumpValidation, ListDumpFiles, ListSnapshots, GetFullStatus, restic check tracking. Server accepts scheduler for next-run time calculation. Sidebar nav updated with 3rd item, dashboard backup card title clickable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,10 @@ package web
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
)
|
||||
|
||||
@@ -157,5 +160,145 @@ func (s *Server) templateFuncMap() template.FuncMap {
|
||||
return "available"
|
||||
}
|
||||
},
|
||||
"timeAgo": func(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "–"
|
||||
}
|
||||
loc, _ := time.LoadLocation("Europe/Budapest")
|
||||
if loc == nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
now := time.Now().In(loc)
|
||||
d := now.Sub(t.In(loc))
|
||||
switch {
|
||||
case d < time.Minute:
|
||||
return "most"
|
||||
case d < time.Hour:
|
||||
return fmt.Sprintf("%d perce", int(d.Minutes()))
|
||||
case d < 24*time.Hour:
|
||||
return fmt.Sprintf("%d órája", int(d.Hours()))
|
||||
case d < 48*time.Hour:
|
||||
return "tegnap"
|
||||
default:
|
||||
return fmt.Sprintf("%d napja", int(d.Hours()/24))
|
||||
}
|
||||
},
|
||||
"fmtTime": func(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "–"
|
||||
}
|
||||
loc, _ := time.LoadLocation("Europe/Budapest")
|
||||
if loc == nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
return t.In(loc).Format("2006-01-02 15:04")
|
||||
},
|
||||
"fmtTimeShort": func(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "–"
|
||||
}
|
||||
loc, _ := time.LoadLocation("Europe/Budapest")
|
||||
if loc == nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
lt := t.In(loc)
|
||||
now := time.Now().In(loc)
|
||||
if lt.Year() == now.Year() && lt.YearDay() == now.YearDay() {
|
||||
return lt.Format("15:04")
|
||||
}
|
||||
return lt.Format("01-02 15:04")
|
||||
},
|
||||
"dbTypeLabel": func(t backup.DBType) string {
|
||||
switch t {
|
||||
case backup.DBTypePostgres:
|
||||
return "PostgreSQL"
|
||||
case backup.DBTypeMariaDB:
|
||||
return "MariaDB"
|
||||
default:
|
||||
return string(t)
|
||||
}
|
||||
},
|
||||
"nextRunLabel": func(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "–"
|
||||
}
|
||||
loc, _ := time.LoadLocation("Europe/Budapest")
|
||||
if loc == nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
lt := t.In(loc)
|
||||
now := time.Now().In(loc)
|
||||
timeStr := lt.Format("15:04")
|
||||
if lt.Year() == now.Year() && lt.YearDay() == now.YearDay() {
|
||||
return "ma " + timeStr
|
||||
}
|
||||
if lt.Year() == now.Year() && lt.YearDay() == now.YearDay()+1 {
|
||||
return "holnap " + timeStr
|
||||
}
|
||||
return lt.Format("2006-01-02") + " " + timeStr
|
||||
},
|
||||
"pruneLabel": func(s string) string {
|
||||
switch strings.ToLower(s) {
|
||||
case "weekly":
|
||||
return "vasárnap"
|
||||
case "daily":
|
||||
return "naponta"
|
||||
case "sunday":
|
||||
return "vasárnap"
|
||||
default:
|
||||
return s
|
||||
}
|
||||
},
|
||||
"nextPruneLabel": func(schedule string) string {
|
||||
loc, _ := time.LoadLocation("Europe/Budapest")
|
||||
if loc == nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
now := time.Now().In(loc)
|
||||
var next time.Time
|
||||
switch strings.ToLower(schedule) {
|
||||
case "daily":
|
||||
next = now.Add(24 * time.Hour)
|
||||
default: // weekly/sunday
|
||||
daysUntilSunday := (7 - int(now.Weekday())) % 7
|
||||
if daysUntilSunday == 0 && now.Hour() >= 4 {
|
||||
daysUntilSunday = 7
|
||||
}
|
||||
next = now.AddDate(0, 0, daysUntilSunday)
|
||||
}
|
||||
return next.Format("2006-01-02")
|
||||
},
|
||||
"fmtDuration": func(d time.Duration) string {
|
||||
if d < time.Second {
|
||||
return "< 1s"
|
||||
}
|
||||
if d < time.Minute {
|
||||
return fmt.Sprintf("%ds", int(d.Seconds()))
|
||||
}
|
||||
return fmt.Sprintf("%dm %ds", int(d.Minutes()), int(d.Seconds())%60)
|
||||
},
|
||||
"fmtBytes": func(b int64) string {
|
||||
const (
|
||||
kb = 1024
|
||||
mb = 1024 * kb
|
||||
gb = 1024 * mb
|
||||
)
|
||||
switch {
|
||||
case b >= int64(gb):
|
||||
return fmt.Sprintf("%.1f GB", float64(b)/float64(gb))
|
||||
case b >= int64(mb):
|
||||
return fmt.Sprintf("%.1f MB", float64(b)/float64(mb))
|
||||
case b >= int64(kb):
|
||||
return fmt.Sprintf("%.1f KB", float64(b)/float64(kb))
|
||||
default:
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
},
|
||||
"shortID": func(id string) string {
|
||||
if len(id) > 8 {
|
||||
return id[:8]
|
||||
}
|
||||
return id
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user