feat(panel): show snapshot disk usage in a wider time-machine modal
Incus's snapshot API already returns each snapshot's size, so the overview now renders a Size column (via a newly exported HumanBytes) and the time machine modal is bumped from 420px to the wide layout. The per-row value is greeted only when snapshots exist; the header ships in the template. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
f79ccb09f0
commit
4bc161c379
4 files changed
+28
-8
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}}</th>
|
||||
// Check the whole conditional so the language branch change doesn't pass.
|
||||
if !strings.Contains(tpl, "大小{{else}}Size{{end}}</th>") && !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"`)
|
||||
|
||||
@@ -411,7 +411,7 @@ footer a{color:var(--muted);text-decoration:underline}
|
||||
</div>
|
||||
|
||||
<div class="overlay" id="snapModal">
|
||||
<div class="modal">
|
||||
<div class="modal modal-wide">
|
||||
<h3>{{if eq .Lang "zh"}}时光机{{else}}Time machine{{end}}</h3>
|
||||
<p class="muted">{{if eq .Lang "zh"}}时光机只能回到过去,不能前往未来。恢复到某个检查点会丢失当前数据,以及在此之后创建的所有检查点。每容器最多 {{.SnapshotLimit}} 个检查点。{{else}}A time machine only goes back in time. Restoring to a checkpoint loses the current data and every checkpoint created after it. Up to {{.SnapshotLimit}} per container.{{end}}</p>
|
||||
<div class="btnrow">
|
||||
@@ -419,12 +419,13 @@ footer a{color:var(--muted);text-decoration:underline}
|
||||
</div>
|
||||
{{if .Snapshots}}
|
||||
<table class="domtable">
|
||||
<thead><tr><th>{{if eq .Lang "zh"}}检查点{{else}}Checkpoint{{end}}</th><th>{{if eq .Lang "zh"}}创建时间(本地时区){{else}}Created (local){{end}}</th><th></th></tr></thead>
|
||||
<thead><tr><th>{{if eq .Lang "zh"}}检查点{{else}}Checkpoint{{end}}</th><th>{{if eq .Lang "zh"}}创建时间(本地时区){{else}}Created (local){{end}}</th><th>{{if eq .Lang "zh"}}大小{{else}}Size{{end}}</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Snapshots}}
|
||||
<tr>
|
||||
<td><code>{{.Name}}</code></td>
|
||||
<td><span class="snapts" data-ts="{{.CreatedAt}}">{{.CreatedAt}}</span></td>
|
||||
<td><span class="muted">{{.Size}}</span></td>
|
||||
<td>
|
||||
<button type="button" class="btn accent" onclick="restoreSnap('{{.Name}}')">{{if eq $.Lang "zh"}}恢复{{else}}Restore{{end}}</button>
|
||||
<button type="button" class="btn danger" onclick="delSnap('{{.Name}}')">{{if eq $.Lang "zh"}}删除{{else}}Delete{{end}}</button>
|
||||
|
||||
Reference in new issue
Block a user