refactor: consolidate backup inventory into sortable structs and collapse index UI sections
Build and Push / build (godump, amd64, linux) (push) Successful in 17s
Build and Push / build (godump, amd64, linux) (push) Successful in 17s
This commit is contained in:
+10
-1
@@ -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,9 +133,11 @@ 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 {
|
||||||
|
if status, exists := m.instances[instCfg.Name]; exists {
|
||||||
result = append(result, status)
|
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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -357,7 +357,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .Databases}}
|
{{if .Databases}}
|
||||||
<div class="table-responsive">
|
<details>
|
||||||
|
<summary style="cursor: pointer; outline: none; color: var(--text-secondary); font-weight: 500;">Show Databases ({{len .Databases}})</summary>
|
||||||
|
<div class="table-responsive" style="margin-top: 1rem;">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -387,6 +389,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user