refactor: consolidate backup inventory into sortable structs and collapse index UI sections
Build and Push / build (godump, amd64, linux) (push) Successful in 17s

This commit is contained in:
2026-06-04 18:25:13 +00:00
parent c4e5eddbc3
commit c3d3b28c64
3 changed files with 76 additions and 43 deletions
+11 -2
View File
@@ -3,6 +3,7 @@ package backup
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"sort"
"sync" "sync"
"time" "time"
@@ -74,6 +75,10 @@ func (s *InstanceStatus) Snapshot() InstanceSnapshot {
}) })
} }
sort.Slice(snap.Databases, func(i, j int) bool {
return snap.Databases[i].Name < snap.Databases[j].Name
})
return snap return snap
} }
@@ -128,8 +133,10 @@ func (m *Manager) GetInstances() []*InstanceStatus {
defer m.mu.RUnlock() defer m.mu.RUnlock()
var result []*InstanceStatus var result []*InstanceStatus
for _, status := range m.instances { for _, instCfg := range m.cfg.Instances {
result = append(result, status) if status, exists := m.instances[instCfg.Name]; exists {
result = append(result, status)
}
} }
return result return result
} }
@@ -252,6 +259,8 @@ func (m *Manager) RunInstance(name string) {
return return
} }
sort.Strings(dbs)
inst.mu.Lock() inst.mu.Lock()
for _, db := range dbs { for _, db := range dbs {
if _, exists := inst.Databases[db]; !exists { if _, exists := inst.Databases[db]; !exists {
+25 -6
View File
@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"time" "time"
@@ -32,7 +33,15 @@ type FileInfo struct {
Timestamp time.Time Timestamp time.Time
} }
type Inventory map[string]map[string][]FileInfo // Instance -> DB -> Files type DBInventory struct {
Name string
Files []FileInfo
}
type InstanceInventory struct {
Name string
Databases []DBInventory
}
func NewServer(cfg *config.Config, manager *backup.Manager) *Server { func NewServer(cfg *config.Config, manager *backup.Manager) *Server {
s := &Server{ s := &Server{
@@ -261,7 +270,7 @@ type TemplateData struct {
AnyRunning bool AnyRunning bool
AuthEnabled bool AuthEnabled bool
Instances []backup.InstanceSnapshot Instances []backup.InstanceSnapshot
Inventory Inventory Inventory []InstanceInventory
} }
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) { func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
@@ -347,14 +356,14 @@ func (s *Server) handleRunInstance(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func (s *Server) getInventory() (Inventory, int64) { func (s *Server) getInventory() ([]InstanceInventory, int64) {
inv := make(Inventory) var inv []InstanceInventory
instances := s.manager.GetInstances() instances := s.manager.GetInstances()
var totalSize int64 var totalSize int64
for _, inst := range instances { for _, inst := range instances {
instName := inst.Config.Name instName := inst.Config.Name
inv[instName] = make(map[string][]FileInfo) var dbInvs []DBInventory
entries, err := os.ReadDir(inst.Config.BackupDir) entries, err := os.ReadDir(inst.Config.BackupDir)
if err != nil { if err != nil {
@@ -390,9 +399,19 @@ func (s *Server) getInventory() (Inventory, int64) {
totalSize += info.Size() totalSize += info.Size()
} }
if len(fileInfos) > 0 { if len(fileInfos) > 0 {
inv[instName][dbName] = fileInfos sort.Slice(fileInfos, func(i, j int) bool {
return fileInfos[i].Timestamp.After(fileInfos[j].Timestamp)
})
dbInvs = append(dbInvs, DBInventory{Name: dbName, Files: fileInfos})
} }
} }
if len(dbInvs) > 0 {
sort.Slice(dbInvs, func(i, j int) bool {
return dbInvs[i].Name < dbInvs[j].Name
})
inv = append(inv, InstanceInventory{Name: instName, Databases: dbInvs})
}
} }
return inv, totalSize return inv, totalSize
} }
+40 -35
View File
@@ -357,36 +357,39 @@
</div> </div>
{{if .Databases}} {{if .Databases}}
<div class="table-responsive"> <details>
<table> <summary style="cursor: pointer; outline: none; color: var(--text-secondary); font-weight: 500;">Show Databases ({{len .Databases}})</summary>
<thead> <div class="table-responsive" style="margin-top: 1rem;">
<tr> <table>
<th>Database</th> <thead>
<th>First Discovered</th> <tr>
<th>Last Backup Time</th> <th>Database</th>
<th>Size</th> <th>First Discovered</th>
<th>Result</th> <th>Last Backup Time</th>
</tr> <th>Size</th>
</thead> <th>Result</th>
<tbody> </tr>
{{range .Databases}} </thead>
<tr> <tbody>
<td>{{.Name}}</td> {{range .Databases}}
<td>{{formatTime .FirstDiscovered}}</td> <tr>
<td>{{formatTime .LastBackupTime}}</td> <td>{{.Name}}</td>
<td>{{if gt .LastBackupSize 0}}{{formatBytes .LastBackupSize}}{{else}}-{{end}}</td> <td>{{formatTime .FirstDiscovered}}</td>
<td> <td>{{formatTime .LastBackupTime}}</td>
{{if .LastBackupResult}} <td>{{if gt .LastBackupSize 0}}{{formatBytes .LastBackupSize}}{{else}}-{{end}}</td>
<span class="badge {{.LastBackupResult}}">{{.LastBackupResult}}</span> <td>
{{else}} {{if .LastBackupResult}}
<span class="badge">Pending</span> <span class="badge {{.LastBackupResult}}">{{.LastBackupResult}}</span>
{{end}} {{else}}
</td> <span class="badge">Pending</span>
</tr> {{end}}
{{end}} </td>
</tbody> </tr>
</table> {{end}}
</div> </tbody>
</table>
</div>
</details>
{{else}} {{else}}
<p style="color: var(--text-secondary); font-size: 0.875rem;">No databases discovered yet.</p> <p style="color: var(--text-secondary); font-size: 0.875rem;">No databases discovered yet.</p>
{{end}} {{end}}
@@ -395,16 +398,18 @@
<div class="inventory-section"> <div class="inventory-section">
<h2>Backup Inventory</h2> <h2>Backup Inventory</h2>
{{range $instName, $dbs := .Inventory}} {{range .Inventory}}
<details class="instance-card" style="padding: 1rem 1.5rem; cursor: pointer;"> <details class="instance-card" style="padding: 1rem 1.5rem; cursor: pointer;">
<summary style="font-size: 1.5rem; font-weight: 500; outline: none; list-style-position: inside;"> <summary style="font-size: 1.5rem; font-weight: 500; outline: none; list-style-position: inside;">
{{$instName}} {{.Name}}
</summary> </summary>
<div style="cursor: default; margin-top: 1.5rem;"> <div style="cursor: default; margin-top: 1.5rem;">
{{range $dbName, $files := $dbs}} {{$instName := .Name}}
{{range .Databases}}
{{$dbName := .Name}}
<details class="db-inventory" style="cursor: pointer;"> <details class="db-inventory" style="cursor: pointer;">
<summary style="font-size: 1.1rem; color: var(--text-secondary); margin: 0.5rem 0; outline: none; list-style-position: inside;"> <summary style="font-size: 1.1rem; color: var(--text-secondary); margin: 0.5rem 0; outline: none; list-style-position: inside;">
{{$dbName}} <span style="font-size: 0.8rem; opacity: 0.7;">({{len $files}} files)</span> {{.Name}} <span style="font-size: 0.8rem; opacity: 0.7;">({{len .Files}} files)</span>
</summary> </summary>
<div style="cursor: default; margin-top: 0.5rem;" class="table-responsive"> <div style="cursor: default; margin-top: 0.5rem;" class="table-responsive">
<table style="table-layout: fixed; width: 100%; min-width: 600px;"> <table style="table-layout: fixed; width: 100%; min-width: 600px;">
@@ -415,7 +420,7 @@
<col style="width: 23%;"> <col style="width: 23%;">
</colgroup> </colgroup>
<tbody> <tbody>
{{range $files}} {{range .Files}}
<tr> <tr>
<td style="word-break: break-all;">{{.Name}}</td> <td style="word-break: break-all;">{{.Name}}</td>
<td>{{formatTime .Timestamp}}</td> <td>{{formatTime .Timestamp}}</td>