diff --git a/src/internal/mgr/batch.go b/src/internal/mgr/batch.go index b9694b0..301d85a 100644 --- a/src/internal/mgr/batch.go +++ b/src/internal/mgr/batch.go @@ -212,6 +212,10 @@ func resourceStateName(state int) string { } } +// HumanBytes renders a byte count as a short human string (e.g. "184 MiB"). +// Exportable because the panel shows snapshot sizes. +func HumanBytes(b int64) string { return humanBytes(b) } + // humanBytes renders a byte count as a short human string (e.g. "184 MiB"). func humanBytes(b int64) string { const unit = 1024 diff --git a/src/internal/panel/panel.go b/src/internal/panel/panel.go index 4fb910f..0e5affd 100644 --- a/src/internal/panel/panel.go +++ b/src/internal/panel/panel.go @@ -114,6 +114,7 @@ type domainRow struct { type snapshotRow struct { Name string CreatedAt string // UTC RFC3339; rendered in the browser's timezone + Size string // human-readable disk usage, e.g. "184 MiB"; may be empty } // sshKeyRow is one public key shown in the SSH-key management panel. @@ -368,9 +369,9 @@ func (s *Server) buildData(u *db.User, msg, errMsg string) pageData { // Snapshots come from Incus (one list call). A failure is non-fatal: the // page still renders, and the snapshot modal shows the empty state. if snaps, err := s.mgr.SnapshotList(u.Name); err == nil { - for _, sn := range snaps { - d.Snapshots = append(d.Snapshots, snapshotRow{Name: sn.Name, CreatedAt: sn.CreatedAt}) - } + for _, sn := range snaps { + d.Snapshots = append(d.Snapshots, snapshotRow{Name: sn.Name, CreatedAt: sn.CreatedAt, Size: mgr.HumanBytes(sn.Size)}) + } } d.SnapshotLimit = s.mgr.SnapshotLimit() keys, _ := s.db.ListSSHKeys(u.ID) diff --git a/src/internal/panel/panel_test.go b/src/internal/panel/panel_test.go index f793479..0c3d11e 100644 --- a/src/internal/panel/panel_test.go +++ b/src/internal/panel/panel_test.go @@ -1019,9 +1019,23 @@ func TestOverviewSnapshotModal(t *testing.T) { t.Errorf("overview missing %q", want) } } - // Size column must NOT be rendered. - if strings.Contains(body, ">Size<") || strings.Contains(body, ">大小<") { - t.Error("snapshot size column should not be rendered") + // With the mocked manager the snapshot list on this page is empty, so the + // table would not be rendered here. Verify the shipped template carries the + // size column header and a per-row size cell; the table body is guarded by + // {{if .Snapshots}}, so this checks the template source itself. + src, err := tmplFS.ReadFile("templates/overview.html") + if err != nil { + t.Fatal(err) + } + tpl := string(src) + // The header is a language-conditional template: + // {{if eq .Lang "zh"}}大小{{else}}Size{{end}} + // Check the whole conditional so the language branch change doesn't pass. + if !strings.Contains(tpl, "大小{{else}}Size{{end}}") && !strings.Contains(tpl, "Size{{else}}") { + t.Error("snapshot size column header should be present in overview.html") + } + if !strings.Contains(tpl, ".Size") { + t.Error("snapshot size cell should be rendered per row in overview.html") } // The snapshot create button must be inside the modal. iModal := strings.Index(body, `id="snapModal"`) diff --git a/src/internal/panel/templates/overview.html b/src/internal/panel/templates/overview.html index 3ad9c49..02f7ec5 100644 --- a/src/internal/panel/templates/overview.html +++ b/src/internal/panel/templates/overview.html @@ -411,7 +411,7 @@ footer a{color:var(--muted);text-decoration:underline}
-