package templates import ( "backea/internal/backup" "backea/templates/layouts" "fmt" "sort" "time" ) // FormatSize formats byte size to human-readable format func FormatSize(size int64) string { const unit = 1024 if size < unit { return fmt.Sprintf("%d B", size) } div, exp := int64(unit), 0 for n := size / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.2f %cB", float64(size)/float64(div), "KMGTPE"[exp]) } // FormatTime formats time to a readable format func FormatTime(t time.Time) string { return t.Format("Jan 02, 2006 15:04:05") } // FormatTimeSince formats the duration since a time in a human-readable way func FormatTimeSince(t time.Time) string { timeSince := time.Since(t) hours := int(timeSince.Hours()) if hours < 1 { return fmt.Sprintf("%d minutes ago", int(timeSince.Minutes())) } return fmt.Sprintf("%d hours ago", hours) } // GetStatusClass returns the appropriate status class based on backup age func GetStatusClass(t time.Time) string { timeSince := time.Since(t) hours := int(timeSince.Hours()) if hours > 72 { return "Failed" } else if hours > 24 { return "Warning" } return "Healthy" } // FormatServiceName returns a display-friendly service name func FormatServiceName(groupName, serviceIndex string) string { if serviceIndex == "" { return groupName } return fmt.Sprintf("%s - %s", groupName, serviceIndex) } // CalculateTotalSize calculates total size of all backups func CalculateTotalSize(backups []backup.BackupInfo) int64 { var total int64 for _, b := range backups { total += b.Size } return total } // CalculateGroupTotalSize calculates total size of all backups for a service group func CalculateGroupTotalSize(serviceGroup map[string][]backup.BackupInfo) int64 { var total int64 for _, backups := range serviceGroup { for _, b := range backups { total += b.Size } } return total } // GetGroupTotalBackupCount returns the total number of backups across all services in a group func GetGroupTotalBackupCount(serviceGroup map[string][]backup.BackupInfo) int { count := 0 for _, backups := range serviceGroup { count += len(backups) } return count } // GetLatestBackupTime returns the most recent backup time for a service group func GetLatestBackupTime(serviceGroup map[string][]backup.BackupInfo) (time.Time, bool) { var latestTime time.Time found := false for _, backups := range serviceGroup { if len(backups) > 0 && (latestTime.IsZero() || backups[0].CreationTime.After(latestTime)) { latestTime = backups[0].CreationTime found = true } } return latestTime, found } // GetGroupStatus returns the status of a service group based on the most recent backup func GetGroupStatus(serviceGroup map[string][]backup.BackupInfo) string { latestTime, found := GetLatestBackupTime(serviceGroup) if !found { return "No Backups" } return GetStatusClass(latestTime) } // ServiceProviderInfo holds the backup strategy info for a service type ServiceProviderInfo struct { Type string Provider string Directory string } // BackupWithService represents a backup with its service identifier type BackupWithService struct { ServiceIndex string Backup backup.BackupInfo } // GetSortedBackups collects all backups from a service group and sorts them by time func GetSortedBackups(serviceGroup map[string][]backup.BackupInfo) []BackupWithService { var allBackups []BackupWithService // Collect all backups with their service indices for serviceIndex, backups := range serviceGroup { for _, b := range backups { allBackups = append(allBackups, BackupWithService{ ServiceIndex: serviceIndex, Backup: b, }) } } // Sort by creation time (newest first) sort.Slice(allBackups, func(i, j int) bool { return allBackups[i].Backup.CreationTime.After(allBackups[j].Backup.CreationTime) }) return allBackups } // Home renders the homepage with lazy-loaded backup information templ Home(serviceBackups map[string]map[string][]backup.BackupInfo, serviceConfigs map[string]map[string]ServiceProviderInfo, sortedGroupNames []string, groupDirectories map[string]string) { @layouts.Base("Backea - Backup Dashboard") {

Welcome to Backea

Unified guardians.

Latest Backups by Service

if len(serviceBackups) == 0 {

No backup services configured or no backups found.

} else {
for _, groupName := range sortedGroupNames {

if directory, exists := groupDirectories[groupName]; exists && directory != "" { { directory } } else { Unknown Directory } { groupName }

if len(serviceBackups[groupName]) > 0 { for serviceIndex := range serviceBackups[groupName] { if providerInfo, exists := serviceConfigs[groupName][serviceIndex]; exists { { providerInfo.Type } if providerInfo.Provider == "b2" || providerInfo.Provider == "backblaze" { B2 Backblaze } else if providerInfo.Provider == "ftp" { FTP } else if providerInfo.Provider == "ssh" || providerInfo.Provider == "sftp" { SSH } else if providerInfo.Provider == "s3" { S3 } else { { providerInfo.Provider } } } } } else { Unknown }

Total Size

--

Backups

--

Last Backup

--

Status

--

}
}
} } // GroupHeaderComponent renders just the group header with up-to-date stats templ GroupHeaderComponent(groupName string, serviceBackups map[string][]backup.BackupInfo, serviceConfigs map[string]ServiceProviderInfo, directory string) {

if directory != "" { { directory } } else { Unknown Directory } { groupName }

if len(serviceBackups) > 0 { for serviceIndex := range serviceBackups { if providerInfo, exists := serviceConfigs[serviceIndex]; exists { { providerInfo.Type } if providerInfo.Provider == "b2" || providerInfo.Provider == "backblaze" { B2 Backblaze } else if providerInfo.Provider == "ftp" { FTP } else if providerInfo.Provider == "ssh" || providerInfo.Provider == "sftp" { SSH } else if providerInfo.Provider == "s3" { S3 } else { { providerInfo.Provider } } } } } else { Unknown }

Total Size

{ FormatSize(CalculateGroupTotalSize(serviceBackups)) }

Backups

{ fmt.Sprintf("%d", GetGroupTotalBackupCount(serviceBackups)) }

Last Backup

if latestTime, found := GetLatestBackupTime(serviceBackups); found {

{ FormatTimeSince(latestTime) }

} else {

Never

}

Status

if status := GetGroupStatus(serviceBackups); status == "No Backups" {

No Backups

} else if status == "Failed" {

Failed

} else if status == "Warning" {

Warning

} else {

Healthy

}
} // Updated table templates with action column templ ServiceGroupBackupsTable(groupName string, serviceGroup map[string][]backup.BackupInfo, serviceConfigs map[string]map[string]ServiceProviderInfo) {
if GetGroupTotalBackupCount(serviceGroup) > 0 {
@renderSortedBackups(groupName, GetSortedBackups(serviceGroup), 5, serviceConfigs)
Service Date Size Type Retention Location Actions
if GetGroupTotalBackupCount(serviceGroup) > 5 {
}
} else {

No backups found for this service.

Backups will appear here when created.

}
} // ServiceGroupAllBackupsTable template for showing all backups templ ServiceGroupAllBackupsTable(groupName string, serviceGroup map[string][]backup.BackupInfo, serviceConfigs map[string]map[string]ServiceProviderInfo) { if GetGroupTotalBackupCount(serviceGroup) > 0 {
@renderSortedBackups(groupName, GetSortedBackups(serviceGroup), 9999, serviceConfigs)
Service Date Size Type Retention Location Actions
} else {

No backups found for this service.

Backups will appear here when created.

}
} // renderSortedBackups with action buttons templ renderSortedBackups(groupName string, sortedBackups []BackupWithService, limit int, serviceConfigs map[string]map[string]ServiceProviderInfo) { for i := 0; i < len(sortedBackups) && i < limit; i++ { { FormatServiceName(groupName, sortedBackups[i].ServiceIndex) } { FormatTime(sortedBackups[i].Backup.CreationTime) } { FormatSize(sortedBackups[i].Backup.Size) } { sortedBackups[i].Backup.Type } { sortedBackups[i].Backup.RetentionTag } if providerInfo, exists := serviceConfigs[groupName][sortedBackups[i].ServiceIndex]; exists { if providerInfo.Provider == "b2" || providerInfo.Provider == "backblaze" { { providerInfo.Type } B2 Backblaze } else if providerInfo.Provider == "ftp" { { providerInfo.Type } FTP } else if providerInfo.Provider == "ssh" || providerInfo.Provider == "sftp" { { providerInfo.Type } SSH } else if providerInfo.Provider == "s3" { { providerInfo.Type } S3 } else { { providerInfo.Type } { providerInfo.Provider } } } else { Unknown } Download } } // backupsTableRowsSorted renders backup rows sorted by creation time across all services templ backupsTableRowsSorted(groupName string, serviceGroup map[string][]backup.BackupInfo, limit int, serviceConfigs map[string]map[string]ServiceProviderInfo) { @renderSortedBackups(groupName, GetSortedBackups(serviceGroup), limit, serviceConfigs) }