From 6dcbb9caefa0b92531b57e3737fd218aedae664d Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Mon, 9 Feb 2026 14:27:41 +1100 Subject: [PATCH] lifecycle diagnostics --- components/core/header_templ.go | 4 +- components/views/index_templ.go | 6 +- components/views/snapshots_templ.go | 46 +++--- components/views/vm_trace.templ | 29 +++- components/views/vm_trace_templ.go | 190 ++++++++++++++-------- db/helpers.go | 238 +++++++++++++++++++++++----- db/helpers_cache_and_index_test.go | 54 +++++++ server/handler/vmTrace.go | 65 +++++++- 8 files changed, 498 insertions(+), 134 deletions(-) diff --git a/components/core/header_templ.go b/components/core/header_templ.go index 3eaae09..b4b2360 100644 --- a/components/core/header_templ.go +++ b/components/core/header_templ.go @@ -38,7 +38,7 @@ func Header() templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/assets/js/web3-charts.js?v=" + version.Value) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/header.templ`, Line: 15, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/core/header.templ`, Line: 15, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -51,7 +51,7 @@ func Header() templ.Component { var templ_7745c5c3_Var3 templ.SafeURL templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs("/assets/css/output@" + version.Value + ".css") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/header.templ`, Line: 16, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/core/header.templ`, Line: 16, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/components/views/index_templ.go b/components/views/index_templ.go index b2d212b..0f0beba 100644 --- a/components/views/index_templ.go +++ b/components/views/index_templ.go @@ -54,7 +54,7 @@ func Index(info BuildInfo) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(info.BuildTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 40, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 40, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -67,7 +67,7 @@ func Index(info BuildInfo) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(info.SHA1Ver) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 44, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 44, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -80,7 +80,7 @@ func Index(info BuildInfo) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(info.GoVersion) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 48, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 48, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/components/views/snapshots_templ.go b/components/views/snapshots_templ.go index fb88a08..56bacc5 100644 --- a/components/views/snapshots_templ.go +++ b/components/views/snapshots_templ.go @@ -166,7 +166,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 60, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 60, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -179,7 +179,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(subtitle) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 61, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 61, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -192,7 +192,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(len(entries)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 69, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 69, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -211,7 +211,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Group) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 84, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 84, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -229,7 +229,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 90, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 90, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -242,7 +242,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Count) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 94, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 94, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -255,7 +255,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te var templ_7745c5c3_Var11 templ.SafeURL templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(entry.Link) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 97, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 97, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -318,7 +318,7 @@ func VcenterList(links []VcenterLink) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(len(links)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 130, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 130, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -336,7 +336,7 @@ func VcenterList(links []VcenterLink) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(link.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 143, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 143, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -349,7 +349,7 @@ func VcenterList(links []VcenterLink) templ.Component { var templ_7745c5c3_Var15 templ.SafeURL templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinURLErrs(link.Link) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 145, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 145, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -412,7 +412,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(vcenter) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 169, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 169, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -425,7 +425,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(meta.TypeLabel) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 170, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 170, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -447,7 +447,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var19).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -460,7 +460,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var21 templ.SafeURL templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs(meta.HourlyLink) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 178, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 178, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -482,7 +482,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var22).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -495,7 +495,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var24 templ.SafeURL templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(meta.DailyLink) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 179, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 179, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -508,7 +508,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(meta.TypeLabel) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 184, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 184, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -521,7 +521,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(len(entries)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 185, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 185, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -539,7 +539,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(chart.ConfigJSON) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 190, Col: 145} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 190, Col: 145} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -562,7 +562,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Snapshot) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 214, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 214, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -575,7 +575,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(entry.VmCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 215, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 215, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -588,7 +588,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(entry.VcpuTotal) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 216, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 216, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -601,7 +601,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(entry.RamTotalGB) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 217, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 217, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { diff --git a/components/views/vm_trace.templ b/components/views/vm_trace.templ index 1bb84ec..eaf1f33 100644 --- a/components/views/vm_trace.templ +++ b/components/views/vm_trace.templ @@ -33,7 +33,17 @@ type VmTraceMeta struct { DailyClass string } -templ VmTracePage(query string, display_query string, vm_id string, vm_uuid string, vm_name string, creationLabel string, deletionLabel string, creationApprox bool, entries []VmTraceEntry, chart VmTraceChart, meta VmTraceMeta) { +type VmTraceDiagnosticLine struct { + Label string + Value string +} + +type VmTraceDiagnostics struct { + Visible bool + Lines []VmTraceDiagnosticLine +} + +templ VmTracePage(query string, display_query string, vm_id string, vm_uuid string, vm_name string, creationLabel string, deletionLabel string, creationApprox bool, entries []VmTraceEntry, chart VmTraceChart, meta VmTraceMeta, diagnostics VmTraceDiagnostics) { @core.Header() @@ -107,6 +117,23 @@ templ VmTracePage(query string, display_query string, vm_id string, vm_uuid stri

{deletionLabel}

+ if diagnostics.Visible && len(diagnostics.Lines) > 0 { +
+ Lifecycle diagnostics +
+ + + for _, line := range diagnostics.Lines { + + + + + } + +
{ line.Label }{ line.Value }
+
+
+ }
diff --git a/components/views/vm_trace_templ.go b/components/views/vm_trace_templ.go index bb36e88..e850e1c 100644 --- a/components/views/vm_trace_templ.go +++ b/components/views/vm_trace_templ.go @@ -41,7 +41,17 @@ type VmTraceMeta struct { DailyClass string } -func VmTracePage(query string, display_query string, vm_id string, vm_uuid string, vm_name string, creationLabel string, deletionLabel string, creationApprox bool, entries []VmTraceEntry, chart VmTraceChart, meta VmTraceMeta) templ.Component { +type VmTraceDiagnosticLine struct { + Label string + Value string +} + +type VmTraceDiagnostics struct { + Visible bool + Lines []VmTraceDiagnosticLine +} + +func VmTracePage(query string, display_query string, vm_id string, vm_uuid string, vm_name string, creationLabel string, deletionLabel string, creationApprox bool, entries []VmTraceEntry, chart VmTraceChart, meta VmTraceMeta, diagnostics VmTraceDiagnostics) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -77,7 +87,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(display_query) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 46, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 56, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -90,7 +100,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(meta.TypeLabel) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 47, Col: 119} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 57, Col: 119} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -103,7 +113,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(meta.ViewType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 54, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 64, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -116,7 +126,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(vm_id) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 57, Col: 123} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 67, Col: 123} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -129,7 +139,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(vm_uuid) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 61, Col: 129} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 71, Col: 129} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -142,7 +152,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(vm_name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 65, Col: 123} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 75, Col: 123} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -164,7 +174,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -177,7 +187,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var10 templ.SafeURL templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(meta.HourlyLink) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 73, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 83, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -199,7 +209,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -212,7 +222,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var13 templ.SafeURL templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(meta.DailyLink) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 74, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 84, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -225,7 +235,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(meta.TypeLabel) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 80, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 90, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -238,7 +248,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(len(entries)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 81, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 91, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -256,7 +266,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(chart.ConfigJSON) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 86, Col: 133} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 96, Col: 133} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -274,7 +284,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(creationLabel) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 100, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 110, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -297,140 +307,186 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(deletionLabel) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 107, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 117, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if diagnostics.Visible && len(diagnostics.Lines) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Lifecycle diagnostics
SnapshotVM NameVmIdVmUuidVcenterResource PoolvCPUsRAM (GB)Disk
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, line := range diagnostics.Lines { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(line.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 128, Col: 70} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(line.Value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 129, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, e := range entries { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
SnapshotVM NameVmIdVmUuidVcenterResource PoolvCPUsRAM (GB)Disk
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(e.Snapshot) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 128, Col: 25} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var20 string - templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 129, Col: 21} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var21 string - templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(e.VmId) + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(e.Snapshot) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 130, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 155, Col: 25} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(e.VmUuid) + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 131, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 156, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var23 string - templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(e.Vcenter) + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(e.VmId) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 132, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 157, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var24 string - templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(e.ResourcePool) + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(e.VmUuid) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 133, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 158, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var25 string - templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(e.VcpuCount) + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(e.Vcenter) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 134, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 159, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(e.RamGB) + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(e.ResourcePool) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 135, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 160, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", e.ProvisionedDisk)) + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(e.VcpuCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 136, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 161, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(e.RamGB) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 162, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", e.ProvisionedDisk)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/vm_trace.templ`, Line: 163, Col: 72} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -438,7 +494,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/db/helpers.go b/db/helpers.go index 94cc715..e82d692 100644 --- a/db/helpers.go +++ b/db/helpers.go @@ -1672,6 +1672,33 @@ type VmLifecycle struct { DeletionTime int64 } +// VmLifecycleSourceDiagnostics captures how one lifecycle source contributed to the final lifecycle decision. +type VmLifecycleSourceDiagnostics struct { + Source string + Used bool + Error string + MatchedRows int64 + FirstSeen int64 + LastSeen int64 + CreationTime int64 + CreationApprox bool + DeletionRows int64 + DeletionMin int64 + DeletionMax int64 + SelectedDeletionTime int64 + StaleDeletionIgnored bool +} + +// VmLifecycleDiagnostics captures per-source lifecycle diagnostics for a VM lookup. +type VmLifecycleDiagnostics struct { + LookupField string + LookupValue string + HourlyCache VmLifecycleSourceDiagnostics + LifecycleCache VmLifecycleSourceDiagnostics + SnapshotFallback VmLifecycleSourceDiagnostics + FinalLifecycle VmLifecycle +} + func vmLookupPredicate(vmID, vmUUID, name string) (string, []interface{}, bool) { vmID = strings.TrimSpace(vmID) vmUUID = strings.TrimSpace(vmUUID) @@ -1688,6 +1715,22 @@ func vmLookupPredicate(vmID, vmUUID, name string) (string, []interface{}, bool) } } +func vmLookupDescriptor(vmID, vmUUID, name string) (string, string) { + vmID = strings.TrimSpace(vmID) + vmUUID = strings.TrimSpace(vmUUID) + name = strings.TrimSpace(name) + switch { + case vmID != "": + return "vm_id", vmID + case vmUUID != "": + return "vm_uuid", vmUUID + case name != "": + return "name", name + default: + return "", "" + } +} + // FetchVmTrace returns combined hourly snapshot records for a VM (by id/uuid/name) ordered by snapshot time. // It prefers the shared vm_hourly_stats history table and falls back to per-snapshot tables. func FetchVmTrace(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) ([]VmTraceRow, error) { @@ -1892,36 +1935,75 @@ WHERE %s // FetchVmLifecycle walks VM history data to determine lifecycle bounds for a VM. // It prefers vm_hourly_stats + vm_lifecycle_cache and falls back to per-snapshot table probes. func FetchVmLifecycle(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, error) { + lifecycle, _, err := FetchVmLifecycleWithDiagnostics(ctx, dbConn, vmID, vmUUID, name) + return lifecycle, err +} + +// FetchVmLifecycleWithDiagnostics returns lifecycle details plus source-level diagnostics. +func FetchVmLifecycleWithDiagnostics(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, VmLifecycleDiagnostics, error) { + lookupField, lookupValue := vmLookupDescriptor(vmID, vmUUID, name) + diagnostics := VmLifecycleDiagnostics{ + LookupField: lookupField, + LookupValue: lookupValue, + HourlyCache: VmLifecycleSourceDiagnostics{ + Source: "vm_hourly_stats", + }, + LifecycleCache: VmLifecycleSourceDiagnostics{ + Source: "vm_lifecycle_cache", + }, + SnapshotFallback: VmLifecycleSourceDiagnostics{ + Source: "snapshot_registry/hourly_tables", + }, + } + if TableExists(ctx, dbConn, "vm_hourly_stats") { - lifecycle, found, err := fetchVmLifecycleFromHourlyCache(ctx, dbConn, vmID, vmUUID, name) + lifecycle, sourceDiag, found, err := fetchVmLifecycleFromHourlyCache(ctx, dbConn, vmID, vmUUID, name) + diagnostics.HourlyCache = sourceDiag if err != nil { + diagnostics.HourlyCache.Error = err.Error() slog.Warn("vm lifecycle cache query failed; falling back to hourly tables", "error", err) } else if found { + diagnostics.HourlyCache.Used = true if TableExists(ctx, dbConn, "vm_lifecycle_cache") { - cached, cachedFound, cacheErr := fetchVmLifecycleFromLifecycleCache(ctx, dbConn, vmID, vmUUID, name) + cached, cachedDiag, cachedFound, cacheErr := fetchVmLifecycleFromLifecycleCache(ctx, dbConn, vmID, vmUUID, name) + diagnostics.LifecycleCache = cachedDiag if cacheErr != nil { + diagnostics.LifecycleCache.Error = cacheErr.Error() slog.Warn("vm lifecycle cache lookup failed", "error", cacheErr) } else if cachedFound { + diagnostics.LifecycleCache.Used = true lifecycle = mergeVmLifecycle(lifecycle, cached) } } - return lifecycle, nil + diagnostics.FinalLifecycle = lifecycle + return lifecycle, diagnostics, nil } } - return fetchVmLifecycleFromSnapshotTables(ctx, dbConn, vmID, vmUUID, name) + lifecycle, fallbackDiag, err := fetchVmLifecycleFromSnapshotTables(ctx, dbConn, vmID, vmUUID, name) + diagnostics.SnapshotFallback = fallbackDiag + if err != nil { + diagnostics.SnapshotFallback.Error = err.Error() + return lifecycle, diagnostics, err + } + diagnostics.SnapshotFallback.Used = true + diagnostics.FinalLifecycle = lifecycle + return lifecycle, diagnostics, nil } -func fetchVmLifecycleFromHourlyCache(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, bool, error) { +func fetchVmLifecycleFromHourlyCache(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, VmLifecycleSourceDiagnostics, bool, error) { + diag := VmLifecycleSourceDiagnostics{Source: "vm_hourly_stats"} matchWhere, args, ok := vmLookupPredicate(vmID, vmUUID, name) if !ok { - return VmLifecycle{}, false, nil + return VmLifecycle{}, diag, false, nil } var row struct { - Rows int64 `db:"rows"` - Creation sql.NullInt64 `db:"creation_time"` - FirstSeen sql.NullInt64 `db:"first_seen"` - LastSeen sql.NullInt64 `db:"last_seen"` - Deletion sql.NullInt64 `db:"deletion_time"` + Rows int64 `db:"rows"` + Creation sql.NullInt64 `db:"creation_time"` + FirstSeen sql.NullInt64 `db:"first_seen"` + LastSeen sql.NullInt64 `db:"last_seen"` + DeletionRows int64 `db:"deletion_rows"` + DeletionMin sql.NullInt64 `db:"deletion_min"` + DeletionMax sql.NullInt64 `db:"deletion_max"` } query := ` SELECT @@ -1929,16 +2011,29 @@ SELECT MIN(NULLIF("CreationTime",0)) AS creation_time, MIN("SnapshotTime") AS first_seen, MAX("SnapshotTime") AS last_seen, - MIN(NULLIF("DeletionTime",0)) AS deletion_time + SUM(CASE WHEN COALESCE("DeletionTime",0) > 0 THEN 1 ELSE 0 END) AS deletion_rows, + MIN(NULLIF("DeletionTime",0)) AS deletion_min, + MAX(NULLIF("DeletionTime",0)) AS deletion_max FROM vm_hourly_stats WHERE ` + matchWhere + ` ` query = dbConn.Rebind(query) if err := getLog(ctx, dbConn, &row, query, args...); err != nil { - return VmLifecycle{}, false, err + diag.Error = err.Error() + return VmLifecycle{}, diag, false, err + } + diag.MatchedRows = row.Rows + diag.FirstSeen = row.FirstSeen.Int64 + diag.LastSeen = row.LastSeen.Int64 + diag.DeletionRows = row.DeletionRows + if row.DeletionMin.Valid { + diag.DeletionMin = row.DeletionMin.Int64 + } + if row.DeletionMax.Valid { + diag.DeletionMax = row.DeletionMax.Int64 } if row.Rows == 0 { - return VmLifecycle{}, false, nil + return VmLifecycle{}, diag, false, nil } lifecycle := VmLifecycle{ FirstSeen: row.FirstSeen.Int64, @@ -1951,38 +2046,70 @@ WHERE ` + matchWhere + ` lifecycle.CreationTime = row.FirstSeen.Int64 lifecycle.CreationApprox = true } - if row.Deletion.Valid && row.Deletion.Int64 > 0 { - lifecycle.DeletionTime = row.Deletion.Int64 + diag.CreationTime = lifecycle.CreationTime + diag.CreationApprox = lifecycle.CreationApprox + if row.DeletionMax.Valid && row.DeletionMax.Int64 > 0 { + if row.LastSeen.Valid && row.LastSeen.Int64 > 0 && row.DeletionMax.Int64 <= row.LastSeen.Int64 { + diag.StaleDeletionIgnored = true + slog.Warn("ignoring stale VM deletion from hourly cache", + "vm_id", vmID, + "vm_uuid", vmUUID, + "name", name, + "deletion_rows", row.DeletionRows, + "deletion_min", row.DeletionMin.Int64, + "deletion_max", row.DeletionMax.Int64, + "last_seen", row.LastSeen.Int64, + ) + } else { + lifecycle.DeletionTime = row.DeletionMax.Int64 + } } - return lifecycle, true, nil + diag.SelectedDeletionTime = lifecycle.DeletionTime + return lifecycle, diag, true, nil } -func fetchVmLifecycleFromLifecycleCache(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, bool, error) { +func fetchVmLifecycleFromLifecycleCache(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, VmLifecycleSourceDiagnostics, bool, error) { + diag := VmLifecycleSourceDiagnostics{Source: "vm_lifecycle_cache"} matchWhere, args, ok := vmLookupPredicate(vmID, vmUUID, name) if !ok { - return VmLifecycle{}, false, nil + return VmLifecycle{}, diag, false, nil } var row struct { - Rows int64 `db:"rows"` - FirstSeen sql.NullInt64 `db:"first_seen"` - LastSeen sql.NullInt64 `db:"last_seen"` - Deletion sql.NullInt64 `db:"deletion_time"` + Rows int64 `db:"rows"` + FirstSeen sql.NullInt64 `db:"first_seen"` + LastSeen sql.NullInt64 `db:"last_seen"` + DeletionRows int64 `db:"deletion_rows"` + DeletionMin sql.NullInt64 `db:"deletion_min"` + DeletionMax sql.NullInt64 `db:"deletion_max"` } query := ` SELECT COUNT(1) AS rows, MIN(NULLIF("FirstSeen",0)) AS first_seen, MAX(NULLIF("LastSeen",0)) AS last_seen, - MIN(NULLIF("DeletedAt",0)) AS deletion_time + SUM(CASE WHEN COALESCE("DeletedAt",0) > 0 THEN 1 ELSE 0 END) AS deletion_rows, + MIN(NULLIF("DeletedAt",0)) AS deletion_min, + MAX(NULLIF("DeletedAt",0)) AS deletion_max FROM vm_lifecycle_cache WHERE ` + matchWhere + ` ` query = dbConn.Rebind(query) if err := getLog(ctx, dbConn, &row, query, args...); err != nil { - return VmLifecycle{}, false, err + diag.Error = err.Error() + return VmLifecycle{}, diag, false, err + } + diag.MatchedRows = row.Rows + diag.FirstSeen = row.FirstSeen.Int64 + diag.LastSeen = row.LastSeen.Int64 + diag.DeletionRows = row.DeletionRows + if row.DeletionMin.Valid { + diag.DeletionMin = row.DeletionMin.Int64 + } + if row.DeletionMax.Valid { + diag.DeletionMax = row.DeletionMax.Int64 } if row.Rows == 0 { - return VmLifecycle{}, false, nil + return VmLifecycle{}, diag, false, nil } lifecycle := VmLifecycle{ FirstSeen: row.FirstSeen.Int64, @@ -1992,10 +2119,26 @@ WHERE ` + matchWhere + ` lifecycle.CreationTime = row.FirstSeen.Int64 lifecycle.CreationApprox = true } - if row.Deletion.Valid && row.Deletion.Int64 > 0 { - lifecycle.DeletionTime = row.Deletion.Int64 + diag.CreationTime = lifecycle.CreationTime + diag.CreationApprox = lifecycle.CreationApprox + if row.DeletionMax.Valid && row.DeletionMax.Int64 > 0 { + if row.LastSeen.Valid && row.LastSeen.Int64 > 0 && row.DeletionMax.Int64 <= row.LastSeen.Int64 { + diag.StaleDeletionIgnored = true + slog.Warn("ignoring stale VM deletion from lifecycle cache", + "vm_id", vmID, + "vm_uuid", vmUUID, + "name", name, + "deletion_rows", row.DeletionRows, + "deletion_min", row.DeletionMin.Int64, + "deletion_max", row.DeletionMax.Int64, + "last_seen", row.LastSeen.Int64, + ) + } else { + lifecycle.DeletionTime = row.DeletionMax.Int64 + } } - return lifecycle, true, nil + diag.SelectedDeletionTime = lifecycle.DeletionTime + return lifecycle, diag, true, nil } func mergeVmLifecycle(base, overlay VmLifecycle) VmLifecycle { @@ -2006,7 +2149,7 @@ func mergeVmLifecycle(base, overlay VmLifecycle) VmLifecycle { if overlay.LastSeen > out.LastSeen { out.LastSeen = overlay.LastSeen } - if overlay.DeletionTime > 0 && (out.DeletionTime == 0 || overlay.DeletionTime < out.DeletionTime) { + if overlay.DeletionTime > 0 && (out.DeletionTime == 0 || overlay.DeletionTime > out.DeletionTime) { out.DeletionTime = overlay.DeletionTime } if overlay.CreationTime > 0 { @@ -2021,14 +2164,18 @@ func mergeVmLifecycle(base, overlay VmLifecycle) VmLifecycle { out.CreationTime = out.FirstSeen out.CreationApprox = true } + if out.DeletionTime > 0 && out.LastSeen > 0 && out.DeletionTime <= out.LastSeen { + out.DeletionTime = 0 + } return out } -func fetchVmLifecycleFromSnapshotTables(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, error) { +func fetchVmLifecycleFromSnapshotTables(ctx context.Context, dbConn *sqlx.DB, vmID, vmUUID, name string) (VmLifecycle, VmLifecycleSourceDiagnostics, error) { var lifecycle VmLifecycle + diag := VmLifecycleSourceDiagnostics{Source: "snapshot_registry/hourly_tables"} matchWhere, args, ok := vmLookupPredicate(vmID, vmUUID, name) if !ok { - return lifecycle, nil + return lifecycle, diag, nil } var tables []struct { TableName string `db:"table_name"` @@ -2040,11 +2187,14 @@ FROM snapshot_registry WHERE snapshot_type = 'hourly' ORDER BY snapshot_time `); err != nil { - return lifecycle, err + diag.Error = err.Error() + return lifecycle, diag, err } minCreation := int64(0) consecutiveMissing := 0 + firstMissingAfterLastSeen := int64(0) + matchedRows := int64(0) for _, t := range tables { if err := ValidateTableName(t.TableName); err != nil { continue @@ -2063,11 +2213,18 @@ WHERE %s continue } if probe.Cnt > 0 { + matchedRows++ if lifecycle.FirstSeen == 0 { lifecycle.FirstSeen = t.SnapshotTime } + if lifecycle.DeletionTime > 0 && t.SnapshotTime > lifecycle.DeletionTime { + diag.StaleDeletionIgnored = true + slog.Warn("ignoring stale VM deletion from snapshot-table fallback", "vm_id", vmID, "vm_uuid", vmUUID, "name", name, "deletion_time", lifecycle.DeletionTime, "seen_again_at", t.SnapshotTime) + lifecycle.DeletionTime = 0 + } lifecycle.LastSeen = t.SnapshotTime consecutiveMissing = 0 + firstMissingAfterLastSeen = 0 if probe.MinCreation.Valid { if minCreation == 0 || probe.MinCreation.Int64 < minCreation { minCreation = probe.MinCreation.Int64 @@ -2075,12 +2232,15 @@ WHERE %s } } else if lifecycle.LastSeen > 0 && lifecycle.DeletionTime == 0 && t.SnapshotTime > lifecycle.LastSeen { consecutiveMissing++ + if firstMissingAfterLastSeen == 0 { + firstMissingAfterLastSeen = t.SnapshotTime + } if consecutiveMissing >= 2 { - lifecycle.DeletionTime = t.SnapshotTime - break + lifecycle.DeletionTime = firstMissingAfterLastSeen } } else { consecutiveMissing = 0 + firstMissingAfterLastSeen = 0 } } if minCreation > 0 { @@ -2090,7 +2250,13 @@ WHERE %s lifecycle.CreationTime = lifecycle.FirstSeen lifecycle.CreationApprox = true } - return lifecycle, nil + diag.MatchedRows = matchedRows + diag.FirstSeen = lifecycle.FirstSeen + diag.LastSeen = lifecycle.LastSeen + diag.CreationTime = lifecycle.CreationTime + diag.CreationApprox = lifecycle.CreationApprox + diag.SelectedDeletionTime = lifecycle.DeletionTime + return lifecycle, diag, nil } // SyncVcenterTotalsFromSnapshots backfills vcenter_totals using hourly snapshot tables in snapshot_registry. diff --git a/db/helpers_cache_and_index_test.go b/db/helpers_cache_and_index_test.go index d910c3d..952d0ed 100644 --- a/db/helpers_cache_and_index_test.go +++ b/db/helpers_cache_and_index_test.go @@ -278,6 +278,60 @@ VALUES (?,?,?,?,?,?,?,?,?,?) } } +func TestFetchVmLifecycleIgnoresStaleDeletionFromHourlyCache(t *testing.T) { + ctx := context.Background() + dbConn := newTestSQLiteDB(t) + + if err := EnsureVmHourlyStats(ctx, dbConn); err != nil { + t.Fatalf("failed to ensure vm_hourly_stats: %v", err) + } + + insertSQL := ` +INSERT INTO vm_hourly_stats ( + "SnapshotTime","Vcenter","VmId","VmUuid","Name","CreationTime","DeletionTime","ResourcePool", + "Datacenter","Cluster","Folder","ProvisionedDisk","VcpuCount","RamGB","IsTemplate","PoweredOn","SrmPlaceholder" +) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) +` + // First row carries an old deletion marker, later row proves VM is still present. + rows := [][]interface{}{ + {int64(1700000000), "vc-a", "vm-1", "uuid-1", "demo-vm", int64(1699999000), int64(1700003600), "Tin", "dc", "cluster", "folder", 100.0, int64(2), int64(4), "FALSE", "TRUE", "FALSE"}, + {int64(1700100000), "vc-a", "vm-1", "uuid-1", "demo-vm", int64(1699999000), int64(0), "Gold", "dc", "cluster", "folder", 120.0, int64(4), int64(8), "FALSE", "TRUE", "FALSE"}, + } + for _, args := range rows { + if _, err := dbConn.ExecContext(ctx, insertSQL, args...); err != nil { + t.Fatalf("failed to insert hourly cache row: %v", err) + } + } + + lifecycle, err := FetchVmLifecycle(ctx, dbConn, "vm-1", "", "") + if err != nil { + t.Fatalf("FetchVmLifecycle failed: %v", err) + } + if lifecycle.LastSeen != 1700100000 { + t.Fatalf("expected LastSeen=1700100000, got %d", lifecycle.LastSeen) + } + if lifecycle.DeletionTime != 0 { + t.Fatalf("expected stale deletion to be ignored, got %d", lifecycle.DeletionTime) + } + + lifecycleDiag, diag, err := FetchVmLifecycleWithDiagnostics(ctx, dbConn, "vm-1", "", "") + if err != nil { + t.Fatalf("FetchVmLifecycleWithDiagnostics failed: %v", err) + } + if lifecycleDiag.DeletionTime != 0 { + t.Fatalf("expected stale deletion to be ignored in diagnostics path, got %d", lifecycleDiag.DeletionTime) + } + if !diag.HourlyCache.StaleDeletionIgnored { + t.Fatalf("expected hourly cache diagnostics to flag stale deletion ignore, got %#v", diag.HourlyCache) + } + if diag.HourlyCache.DeletionMax != 1700003600 { + t.Fatalf("expected hourly cache deletion max 1700003600, got %d", diag.HourlyCache.DeletionMax) + } + if diag.FinalLifecycle.LastSeen != 1700100000 || diag.FinalLifecycle.DeletionTime != 0 { + t.Fatalf("unexpected final diagnostics lifecycle: %#v", diag.FinalLifecycle) + } +} + func TestParseHourlySnapshotUnix(t *testing.T) { cases := []struct { table string diff --git a/server/handler/vmTrace.go b/server/handler/vmTrace.go index c3bfccf..0da3ff9 100644 --- a/server/handler/vmTrace.go +++ b/server/handler/vmTrace.go @@ -35,6 +35,7 @@ func (h *Handler) VmTrace(w http.ResponseWriter, r *http.Request) { var entries []views.VmTraceEntry chart := views.VmTraceChart{} + diagnostics := views.VmTraceDiagnostics{} queryLabel := firstNonEmpty(vmID, vmUUID, name) displayQuery := "" if queryLabel != "" { @@ -47,10 +48,11 @@ func (h *Handler) VmTrace(w http.ResponseWriter, r *http.Request) { // Only fetch data when a query is provided; otherwise render empty page with form. if vmID != "" || vmUUID != "" || name != "" { h.Logger.Info("vm trace request", "view", viewType, "vm_id", vmID, "vm_uuid", vmUUID, "name", name) - lifecycle, lifeErr := db.FetchVmLifecycle(ctx, h.Database.DB(), vmID, vmUUID, name) + lifecycle, lifecycleDiag, lifeErr := db.FetchVmLifecycleWithDiagnostics(ctx, h.Database.DB(), vmID, vmUUID, name) if lifeErr != nil { h.Logger.Warn("failed to fetch VM lifecycle", "error", lifeErr) } + diagnostics = buildVmTraceDiagnostics(lifecycleDiag) var ( rows []db.VmTraceRow err error @@ -114,7 +116,7 @@ func (h *Handler) VmTrace(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "text/html; charset=utf-8") - if err := views.VmTracePage(queryLabel, displayQuery, vmID, vmUUID, name, creationLabel, deletionLabel, creationApprox, entries, chart, meta).Render(ctx, w); err != nil { + if err := views.VmTracePage(queryLabel, displayQuery, vmID, vmUUID, name, creationLabel, deletionLabel, creationApprox, entries, chart, meta, diagnostics).Render(ctx, w); err != nil { http.Error(w, "Failed to render template", http.StatusInternalServerError) } } @@ -155,6 +157,65 @@ func buildVmTraceMeta(viewType, vmID, vmUUID, name string) views.VmTraceMeta { return meta } +func buildVmTraceDiagnostics(diag db.VmLifecycleDiagnostics) views.VmTraceDiagnostics { + lines := make([]views.VmTraceDiagnosticLine, 0, 24) + lookup := "-" + if diag.LookupField != "" { + lookup = diag.LookupField + "=" + diag.LookupValue + } + lines = append(lines, views.VmTraceDiagnosticLine{Label: "Lookup", Value: lookup}) + lines = append(lines, views.VmTraceDiagnosticLine{Label: "Final First Seen", Value: formatDiagUnix(diag.FinalLifecycle.FirstSeen)}) + lines = append(lines, views.VmTraceDiagnosticLine{Label: "Final Last Seen", Value: formatDiagUnix(diag.FinalLifecycle.LastSeen)}) + lines = append(lines, views.VmTraceDiagnosticLine{Label: "Final Creation Time", Value: formatDiagUnix(diag.FinalLifecycle.CreationTime)}) + lines = append(lines, views.VmTraceDiagnosticLine{Label: "Final Creation Approx", Value: fmt.Sprintf("%t", diag.FinalLifecycle.CreationApprox)}) + lines = append(lines, views.VmTraceDiagnosticLine{Label: "Final Deletion Time", Value: formatDiagUnix(diag.FinalLifecycle.DeletionTime)}) + + lines = append(lines, summarizeLifecycleSource(diag.HourlyCache)...) + lines = append(lines, summarizeLifecycleSource(diag.LifecycleCache)...) + lines = append(lines, summarizeLifecycleSource(diag.SnapshotFallback)...) + + return views.VmTraceDiagnostics{ + Visible: len(lines) > 0, + Lines: lines, + } +} + +func summarizeLifecycleSource(src db.VmLifecycleSourceDiagnostics) []views.VmTraceDiagnosticLine { + prefix := src.Source + if strings.TrimSpace(prefix) == "" { + prefix = "source" + } + out := []views.VmTraceDiagnosticLine{ + {Label: prefix + " used", Value: fmt.Sprintf("%t", src.Used)}, + {Label: prefix + " error", Value: defaultString(src.Error, "-")}, + {Label: prefix + " matched rows", Value: fmt.Sprintf("%d", src.MatchedRows)}, + {Label: prefix + " first seen", Value: formatDiagUnix(src.FirstSeen)}, + {Label: prefix + " last seen", Value: formatDiagUnix(src.LastSeen)}, + {Label: prefix + " creation", Value: formatDiagUnix(src.CreationTime)}, + {Label: prefix + " creation approx", Value: fmt.Sprintf("%t", src.CreationApprox)}, + {Label: prefix + " deletion rows", Value: fmt.Sprintf("%d", src.DeletionRows)}, + {Label: prefix + " deletion min", Value: formatDiagUnix(src.DeletionMin)}, + {Label: prefix + " deletion max", Value: formatDiagUnix(src.DeletionMax)}, + {Label: prefix + " selected deletion", Value: formatDiagUnix(src.SelectedDeletionTime)}, + {Label: prefix + " stale deletion ignored", Value: fmt.Sprintf("%t", src.StaleDeletionIgnored)}, + } + return out +} + +func formatDiagUnix(ts int64) string { + if ts <= 0 { + return "-" + } + return fmt.Sprintf("%s (%d)", time.Unix(ts, 0).Local().Format("2006-01-02 15:04:05"), ts) +} + +func defaultString(value string, fallback string) string { + if strings.TrimSpace(value) == "" { + return fallback + } + return value +} + func buildVmTraceChart(entries []views.VmTraceEntry) views.VmTraceChart { if len(entries) == 0 { return views.VmTraceChart{}