@@ -1,8 +1,8 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
templ Footer() {
|
templ Footer() {
|
||||||
<footer class="fixed p-1 bottom-0 bg-gray-100 w-full border-t">
|
<footer class="web2-footer" role="contentinfo">
|
||||||
<div class="rounded-lg p-4 text-xs italic text-gray-700 text-center">
|
<div class="web2-footer-inner">
|
||||||
© Nathan Coad (nathan.coad@dell.com)
|
© Nathan Coad (nathan.coad@dell.com)
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.3.977
|
// templ: version: v0.3.1001
|
||||||
package core
|
package core
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -29,7 +29,7 @@ func Footer() templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<footer class=\"fixed p-1 bottom-0 bg-gray-100 w-full border-t\"><div class=\"rounded-lg p-4 text-xs italic text-gray-700 text-center\">© Nathan Coad (nathan.coad@dell.com)</div></footer>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<footer class=\"web2-footer\" role=\"contentinfo\"><div class=\"web2-footer-inner\">© Nathan Coad (nathan.coad@dell.com)</div></footer>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ templ Header() {
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<meta name="description" content="vCTP API endpoint"/>
|
<meta name="description" content="vCTP dashboard and API endpoint"/>
|
||||||
|
<meta name="color-scheme" content="light"/>
|
||||||
|
<meta name="theme-color" content="#1b61c9"/>
|
||||||
<title>vCTP API</title>
|
<title>vCTP API</title>
|
||||||
<link rel="icon" href="/favicon.ico"/>
|
<link rel="icon" href="/favicon.ico"/>
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.3.977
|
// templ: version: v0.3.1001
|
||||||
package core
|
package core
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -31,14 +31,14 @@ func Header() templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"vCTP API endpoint\"><title>vCTP API</title><link rel=\"icon\" href=\"/favicon.ico\"><link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\"><link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\"><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><script src=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"vCTP dashboard and API endpoint\"><meta name=\"color-scheme\" content=\"light\"><meta name=\"theme-color\" content=\"#1b61c9\"><title>vCTP API</title><link rel=\"icon\" href=\"/favicon.ico\"><link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\"><link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\"><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><script src=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/assets/js/web3-charts.js?v=" + version.Value)
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/assets/js/web3-charts.js?v=" + version.Value)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `core/header.templ`, Line: 17, Col: 62}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -51,7 +51,7 @@ func Header() templ.Component {
|
|||||||
var templ_7745c5c3_Var3 templ.SafeURL
|
var templ_7745c5c3_Var3 templ.SafeURL
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs("/assets/css/output@" + version.Value + ".css")
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs("/assets/css/output@" + version.Value + ".css")
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `core/header.templ`, Line: 18, Col: 61}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
type ActionLink struct {
|
||||||
|
Label string
|
||||||
|
Href string
|
||||||
|
Class string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SegmentedLink struct {
|
||||||
|
Label string
|
||||||
|
Href string
|
||||||
|
Class string
|
||||||
|
}
|
||||||
|
|
||||||
|
templ PageHeader(pill string, title string, subtitle string, actions []ActionLink) {
|
||||||
|
<div class="web2-page-head-row">
|
||||||
|
<div class="web2-head-copy">
|
||||||
|
if pill != "" {
|
||||||
|
<div class="web2-pill">{ pill }</div>
|
||||||
|
}
|
||||||
|
<h1 class="web2-page-title">{ title }</h1>
|
||||||
|
if subtitle != "" {
|
||||||
|
<p class="web2-page-subtitle">{ subtitle }</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
if len(actions) > 0 {
|
||||||
|
<div class="web2-actions">
|
||||||
|
for _, action := range actions {
|
||||||
|
<a class={ action.Class } href={ action.Href }>{ action.Label }</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SegmentedActions(actions []SegmentedLink) {
|
||||||
|
if len(actions) > 0 {
|
||||||
|
<div class="web3-button-group">
|
||||||
|
for _, action := range actions {
|
||||||
|
<a class={ action.Class } href={ action.Href }>{ action.Label }</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SectionHead(title string, badge string) {
|
||||||
|
<div class="web2-section-head">
|
||||||
|
<h2>{ title }</h2>
|
||||||
|
if badge != "" {
|
||||||
|
<span class="web2-badge">{ badge }</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import (
|
import "vctp/components/core"
|
||||||
"vctp/components/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildInfo struct {
|
type BuildInfo struct {
|
||||||
BuildTime string
|
BuildTime string
|
||||||
@@ -15,59 +13,57 @@ templ Index(info BuildInfo) {
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen web2-bg">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow web2-shell space-y-8">
|
<main class="flex-grow web2-shell web2-card-grid">
|
||||||
<section class="web2-header">
|
<section class="web2-header web2-page-head">
|
||||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
<div class="web2-page-head-row">
|
||||||
<div>
|
<div class="web2-head-copy">
|
||||||
<div class="web2-pill">vCTP Console</div>
|
<div class="web2-pill">vCTP Console</div>
|
||||||
<h1 class="mt-3 text-4xl font-bold">Chargeback Intelligence Dashboard</h1>
|
<h1 class="web2-page-title">Chargeback Intelligence Dashboard</h1>
|
||||||
<p class="mt-2 text-sm text-slate-600">Point in time snapshots of consumption with LDAP/JWT protected API access.</p>
|
<p class="web2-page-subtitle">Point-in-time snapshots of vSphere consumption with LDAP and JWT-protected API access.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="web2-button-group">
|
<div class="web2-actions">
|
||||||
<a class="web2-button" href="/snapshots/hourly">Hourly Snapshots</a>
|
<a class="web2-button" href="/snapshots/hourly">Hourly Snapshots</a>
|
||||||
<a class="web2-button" href="/snapshots/daily">Daily Snapshots</a>
|
<a class="web2-button" href="/snapshots/daily">Daily Snapshots</a>
|
||||||
<a class="web2-button" href="/snapshots/monthly">Monthly Snapshots</a>
|
<a class="web2-button" href="/snapshots/monthly">Monthly Snapshots</a>
|
||||||
<a class="web2-button" href="/vm/trace">VM Trace</a>
|
<a class="web2-button" href="/vm/trace">VM Trace</a>
|
||||||
<a class="web2-button" href="/vcenters">vCenters</a>
|
<a class="web2-button secondary" href="/vcenters">vCenters</a>
|
||||||
<a class="web2-button" href="/swagger/">Swagger UI</a>
|
<a class="web2-button secondary" href="/swagger/">Swagger UI</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="web2-note">
|
||||||
|
When authentication is enabled, obtain a token from <code class="web2-code">POST /api/auth/login</code> and send it as <code class="web2-code">Authorization: Bearer <token></code>. Role policy: <code class="web2-code">viewer</code> covers read/report APIs, <code class="web2-code">admin</code> covers mutating/admin APIs (and includes viewer). UI pages and <code class="web2-code">/metrics</code> remain public.
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section class="web2-kpi-grid">
|
||||||
<section class="grid gap-6 md:grid-cols-3">
|
|
||||||
<div class="web2-card">
|
<div class="web2-card">
|
||||||
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Build Time</p>
|
<p class="web2-kpi-label">Build Time</p>
|
||||||
<p class="mt-3 text-xl font-semibold">{info.BuildTime}</p>
|
<p class="web2-kpi-value">{ info.BuildTime }</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="web2-card">
|
<div class="web2-card">
|
||||||
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">SHA1 Version</p>
|
<p class="web2-kpi-label">SHA1 Version</p>
|
||||||
<p class="mt-3 text-xl font-semibold">{info.SHA1Ver}</p>
|
<p class="web2-kpi-value">{ info.SHA1Ver }</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="web2-card">
|
<div class="web2-card">
|
||||||
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Go Runtime</p>
|
<p class="web2-kpi-label">Go Runtime</p>
|
||||||
<p class="mt-3 text-xl font-semibold">{info.GoVersion}</p>
|
<p class="web2-kpi-value">{ info.GoVersion }</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="grid gap-6 lg:grid-cols-3">
|
<section class="grid gap-6 lg:grid-cols-3">
|
||||||
<div class="web2-card">
|
<div class="web2-card">
|
||||||
<h2 class="text-lg font-semibold mb-2">Overview</h2>
|
<h2 class="mb-2">Overview</h2>
|
||||||
<p class="mt-2 text-sm text-slate-600">
|
<p class="web2-page-subtitle">
|
||||||
vCTP is a vSphere Chargeback Tracking Platform.
|
vCTP is a vSphere Chargeback Tracking Platform.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-sm text-slate-600">
|
<p class="web2-page-subtitle">
|
||||||
Use fast vCenter totals views (Daily Aggregated and Hourly Detail 45d) and VM Trace views (Hourly Detail and Daily Aggregated) to move between long-range trends and granular timelines.
|
Use fast vCenter totals views (Daily Aggregated and Hourly Detail 45d) and VM Trace views (Hourly Detail and Daily Aggregated) to move between long-range trends and granular timelines.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-sm text-slate-600">
|
<p class="web2-page-subtitle">
|
||||||
When authentication is enabled, obtain a token from <code class="web2-code">POST /api/auth/login</code> and send it as <code class="web2-code">Authorization: Bearer <token></code>.
|
Use <code class="web2-code">/api/auth/me</code> to inspect active claims and roles during integration and diagnostics.
|
||||||
</p>
|
|
||||||
<p class="mt-2 text-sm text-slate-600">
|
|
||||||
Role policy: <code class="web2-code">viewer</code> role covers read/report APIs, and <code class="web2-code">admin</code> role covers mutating/admin APIs (<code class="web2-code">admin</code> also grants viewer access). UI pages and <code class="web2-code">/metrics</code> remain public.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="web2-card">
|
<div class="web2-card">
|
||||||
<h2 class="text-lg font-semibold mb-2">Snapshots and Reports</h2>
|
<h2 class="mb-2">Snapshots and Reports</h2>
|
||||||
<div class="mt-3 text-sm text-slate-600 web2-paragraphs">
|
<div class="web2-paragraphs web2-page-subtitle">
|
||||||
<p>Hourly snapshots capture inventory per vCenter (concurrency via <code class="web2-code">hourly_snapshot_concurrency</code>), then daily and monthly summaries are derived from those snapshots.</p>
|
<p>Hourly snapshots capture inventory per vCenter (concurrency via <code class="web2-code">hourly_snapshot_concurrency</code>), then daily and monthly summaries are derived from those snapshots.</p>
|
||||||
<p><strong>Hourly tracks:</strong> VM identity (<code class="web2-code">InventoryId</code>, <code class="web2-code">Name</code>, <code class="web2-code">VmId</code>, <code class="web2-code">VmUuid</code>, <code class="web2-code">Vcenter</code>, <code class="web2-code">EventKey</code>, <code class="web2-code">CloudId</code>), lifecycle (<code class="web2-code">CreationTime</code>, <code class="web2-code">DeletionTime</code>, <code class="web2-code">SnapshotTime</code>), placement (<code class="web2-code">Datacenter</code>, <code class="web2-code">Cluster</code>, <code class="web2-code">Folder</code>, <code class="web2-code">ResourcePool</code>), and sizing/state (<code class="web2-code">VcpuCount</code>, <code class="web2-code">RamGB</code>, <code class="web2-code">ProvisionedDisk</code>, <code class="web2-code">PoweredOn</code>, <code class="web2-code">IsTemplate</code>, <code class="web2-code">SrmPlaceholder</code>).</p>
|
<p><strong>Hourly tracks:</strong> VM identity (<code class="web2-code">InventoryId</code>, <code class="web2-code">Name</code>, <code class="web2-code">VmId</code>, <code class="web2-code">VmUuid</code>, <code class="web2-code">Vcenter</code>, <code class="web2-code">EventKey</code>, <code class="web2-code">CloudId</code>), lifecycle (<code class="web2-code">CreationTime</code>, <code class="web2-code">DeletionTime</code>, <code class="web2-code">SnapshotTime</code>), placement (<code class="web2-code">Datacenter</code>, <code class="web2-code">Cluster</code>, <code class="web2-code">Folder</code>, <code class="web2-code">ResourcePool</code>), and sizing/state (<code class="web2-code">VcpuCount</code>, <code class="web2-code">RamGB</code>, <code class="web2-code">ProvisionedDisk</code>, <code class="web2-code">PoweredOn</code>, <code class="web2-code">IsTemplate</code>, <code class="web2-code">SrmPlaceholder</code>).</p>
|
||||||
<p><strong>Daily tracks:</strong> <code class="web2-code">SamplesPresent</code>, <code class="web2-code">TotalSamples</code>, <code class="web2-code">AvgIsPresent</code>, <code class="web2-code">AvgVcpuCount</code>, <code class="web2-code">AvgRamGB</code>, <code class="web2-code">AvgProvisionedDisk</code>, <code class="web2-code">PoolTinPct</code>, <code class="web2-code">PoolBronzePct</code>, <code class="web2-code">PoolSilverPct</code>, <code class="web2-code">PoolGoldPct</code>, plus chargeback totals columns <code class="web2-code">Tin</code>, <code class="web2-code">Bronze</code>, <code class="web2-code">Silver</code>, <code class="web2-code">Gold</code>.</p>
|
<p><strong>Daily tracks:</strong> <code class="web2-code">SamplesPresent</code>, <code class="web2-code">TotalSamples</code>, <code class="web2-code">AvgIsPresent</code>, <code class="web2-code">AvgVcpuCount</code>, <code class="web2-code">AvgRamGB</code>, <code class="web2-code">AvgProvisionedDisk</code>, <code class="web2-code">PoolTinPct</code>, <code class="web2-code">PoolBronzePct</code>, <code class="web2-code">PoolSilverPct</code>, <code class="web2-code">PoolGoldPct</code>, plus chargeback totals columns <code class="web2-code">Tin</code>, <code class="web2-code">Bronze</code>, <code class="web2-code">Silver</code>, <code class="web2-code">Gold</code>.</p>
|
||||||
@@ -81,8 +77,8 @@ templ Index(info BuildInfo) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="web2-card">
|
<div class="web2-card">
|
||||||
<h2 class="text-lg font-semibold mb-2">Prorating and Aggregation</h2>
|
<h2 class="mb-2">Prorating and Aggregation</h2>
|
||||||
<div class="mt-3 space-y-2 text-sm text-slate-600 web2-paragraphs">
|
<div class="web2-paragraphs web2-page-subtitle">
|
||||||
<p><code class="web2-code">SamplesPresent</code> is the count of snapshots in which the VM appears; <code class="web2-code">TotalSamples</code> is the count of unique snapshot times for that vCenter/day.</p>
|
<p><code class="web2-code">SamplesPresent</code> is the count of snapshots in which the VM appears; <code class="web2-code">TotalSamples</code> is the count of unique snapshot times for that vCenter/day.</p>
|
||||||
<p><code class="web2-code">AvgIsPresent = SamplesPresent / TotalSamples</code> (0 when <code class="web2-code">TotalSamples</code> is 0).</p>
|
<p><code class="web2-code">AvgIsPresent = SamplesPresent / TotalSamples</code> (0 when <code class="web2-code">TotalSamples</code> is 0).</p>
|
||||||
<p>Daily <code class="web2-code">AvgVcpuCount</code>, <code class="web2-code">AvgRamGB</code>, and <code class="web2-code">AvgProvisionedDisk</code> are per-sample sums divided by <code class="web2-code">TotalSamples</code> (time-weighted).</p>
|
<p>Daily <code class="web2-code">AvgVcpuCount</code>, <code class="web2-code">AvgRamGB</code>, and <code class="web2-code">AvgProvisionedDisk</code> are per-sample sums divided by <code class="web2-code">TotalSamples</code> (time-weighted).</p>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,9 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import "vctp/components/core"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"vctp/components/core"
|
||||||
|
)
|
||||||
|
|
||||||
type SnapshotEntry struct {
|
type SnapshotEntry struct {
|
||||||
Label string
|
Label string
|
||||||
@@ -52,23 +55,24 @@ templ SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) {
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen web2-bg">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow web2-shell space-y-8">
|
<main class="flex-grow web2-shell web2-card-grid">
|
||||||
<section class="web2-header">
|
<section class="web2-header web2-page-head">
|
||||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
@core.PageHeader(
|
||||||
<div>
|
"Snapshot Library",
|
||||||
<div class="web2-pill">Snapshot Library</div>
|
title,
|
||||||
<h1 class="mt-3 text-4xl font-bold">{ title }</h1>
|
subtitle,
|
||||||
<p class="mt-2 text-sm text-slate-600">{ subtitle }</p>
|
[]core.ActionLink{
|
||||||
</div>
|
{
|
||||||
<a class="web2-button" href="/">Back to Dashboard</a>
|
Label: "Back to Dashboard",
|
||||||
</div>
|
Href: "/",
|
||||||
|
Class: "web2-button secondary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
</section>
|
</section>
|
||||||
<section class="web2-card">
|
<section class="web2-card">
|
||||||
<div class="flex items-center justify-between gap-3 mb-4 flex-wrap">
|
@core.SectionHead("Available Exports", fmt.Sprintf("%d files", len(entries)))
|
||||||
<h2 class="text-lg font-semibold">Available Exports</h2>
|
<div class="web2-table-shell">
|
||||||
<span class="web2-badge">{ len(entries) } files</span>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-hidden border border-slate-200 rounded">
|
|
||||||
<table class="web2-table">
|
<table class="web2-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -81,13 +85,13 @@ templ SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) {
|
|||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
if entry.Group != "" && (i == 0 || entries[i-1].Group != entry.Group) {
|
if entry.Group != "" && (i == 0 || entries[i-1].Group != entry.Group) {
|
||||||
<tr class="web2-group-row">
|
<tr class="web2-group-row">
|
||||||
<td colspan="3" class="font-semibold text-slate-700">{ entry.Group }</td>
|
<td colspan="3" class="font-semibold">{ entry.Group }</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-semibold text-slate-700">{ entry.Label }</span>
|
<span class="text-sm font-semibold">{ entry.Label }</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -113,23 +117,24 @@ templ VcenterList(links []VcenterLink) {
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen web2-bg">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow web2-shell space-y-8">
|
<main class="flex-grow web2-shell web2-card-grid">
|
||||||
<section class="web2-header">
|
<section class="web2-header web2-page-head">
|
||||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
@core.PageHeader(
|
||||||
<div>
|
"vCenter Inventory",
|
||||||
<div class="web2-pill">vCenter Inventory</div>
|
"Monitored vCenters",
|
||||||
<h1 class="mt-3 text-4xl font-bold">Monitored vCenters</h1>
|
"Select a vCenter to view snapshot totals over time.",
|
||||||
<p class="mt-2 text-sm text-slate-600">Select a vCenter to view snapshot totals over time.</p>
|
[]core.ActionLink{
|
||||||
</div>
|
{
|
||||||
<a class="web2-button" href="/">Back to Dashboard</a>
|
Label: "Back to Dashboard",
|
||||||
</div>
|
Href: "/",
|
||||||
|
Class: "web2-button secondary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
</section>
|
</section>
|
||||||
<section class="web2-card">
|
<section class="web2-card">
|
||||||
<div class="flex items-center justify-between gap-3 mb-4 flex-wrap">
|
@core.SectionHead("vCenters", fmt.Sprintf("%d total", len(links)))
|
||||||
<h2 class="text-lg font-semibold">vCenters</h2>
|
<div class="web2-table-shell">
|
||||||
<span class="web2-badge">{ len(links) } total</span>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-hidden border border-slate-200 rounded">
|
|
||||||
<table class="web2-table">
|
<table class="web2-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -140,7 +145,7 @@ templ VcenterList(links []VcenterLink) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
<tr>
|
<tr>
|
||||||
<td class="font-semibold text-slate-700">{ link.Name }</td>
|
<td class="font-semibold">{ link.Name }</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a class="web2-link" href={ link.Link }>View Totals</a>
|
<a class="web2-link" href={ link.Link }>View Totals</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -161,29 +166,42 @@ templ VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcen
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen web2-bg">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow web2-shell web2-shell-wide space-y-8 max-w-screen-2xl mx-auto" style="max-width: 1400px; width: 100%;">
|
<main class="flex-grow web2-shell web2-shell-wide web2-card-grid">
|
||||||
<section class="web2-header">
|
<section class="web2-header web2-page-head">
|
||||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
@core.PageHeader(
|
||||||
<div>
|
"vCenter Totals",
|
||||||
<div class="web2-pill">vCenter Totals</div>
|
"Totals for "+vcenter,
|
||||||
<h1 class="mt-3 text-4xl font-bold">Totals for { vcenter }</h1>
|
meta.TypeLabel+" snapshots of VM count, vCPU, and RAM over time.",
|
||||||
<p class="mt-2 text-sm text-slate-600">{ meta.TypeLabel } snapshots of VM count, vCPU, and RAM over time.</p>
|
[]core.ActionLink{
|
||||||
</div>
|
{
|
||||||
<div class="flex gap-3">
|
Label: "All vCenters",
|
||||||
<a class="web2-button secondary" href="/vcenters">All vCenters</a>
|
Href: "/vcenters",
|
||||||
<a class="web2-button" href="/">Dashboard</a>
|
Class: "web2-button secondary",
|
||||||
</div>
|
},
|
||||||
</div>
|
{
|
||||||
<div class="web3-button-group mt-8 mb-3">
|
Label: "Dashboard",
|
||||||
<a class={ meta.HourlyClass } href={ meta.HourlyLink }>Hourly Detail (45d)</a>
|
Href: "/",
|
||||||
<a class={ meta.DailyClass } href={ meta.DailyLink }>Daily Aggregated</a>
|
Class: "web2-button",
|
||||||
</div>
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@core.SegmentedActions(
|
||||||
|
[]core.SegmentedLink{
|
||||||
|
{
|
||||||
|
Label: "Hourly Detail (45d)",
|
||||||
|
Href: meta.HourlyLink,
|
||||||
|
Class: meta.HourlyClass,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Label: "Daily Aggregated",
|
||||||
|
Href: meta.DailyLink,
|
||||||
|
Class: meta.DailyClass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
</section>
|
</section>
|
||||||
<section class="web2-card">
|
<section class="web2-card">
|
||||||
<div class="flex items-center justify-between gap-3 mb-4 flex-wrap">
|
@core.SectionHead(meta.TypeLabel+" Snapshots", fmt.Sprintf("%d records", len(entries)))
|
||||||
<h2 class="text-lg font-semibold">{ meta.TypeLabel } Snapshots</h2>
|
|
||||||
<span class="web2-badge">{ len(entries) } records</span>
|
|
||||||
</div>
|
|
||||||
if chart.ConfigJSON != "" {
|
if chart.ConfigJSON != "" {
|
||||||
<div class="mb-6 overflow-auto">
|
<div class="mb-6 overflow-auto">
|
||||||
<div class="web3-chart-frame">
|
<div class="web3-chart-frame">
|
||||||
@@ -198,7 +216,7 @@ templ VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcen
|
|||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="overflow-hidden border border-slate-200 rounded">
|
<div class="web2-table-shell">
|
||||||
<table class="web2-table">
|
<table class="web2-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
+228
-300
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.3.977
|
// templ: version: v0.3.1001
|
||||||
package views
|
package views
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -8,7 +8,10 @@ package views
|
|||||||
import "github.com/a-h/templ"
|
import "github.com/a-h/templ"
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
import "vctp/components/core"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"vctp/components/core"
|
||||||
|
)
|
||||||
|
|
||||||
type SnapshotEntry struct {
|
type SnapshotEntry struct {
|
||||||
Label string
|
Label string
|
||||||
@@ -159,114 +162,102 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell space-y-8\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">Snapshot Library</div><h1 class=\"mt-3 text-4xl font-bold\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell web2-card-grid\"><section class=\"web2-header web2-page-head\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var5 string
|
templ_7745c5c3_Err = core.PageHeader(
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
"Snapshot Library",
|
||||||
if templ_7745c5c3_Err != nil {
|
title,
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 60, Col: 50}
|
subtitle,
|
||||||
}
|
[]core.ActionLink{
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
{
|
||||||
|
Label: "Back to Dashboard",
|
||||||
|
Href: "/",
|
||||||
|
Class: "web2-button secondary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-sm text-slate-600\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</section><section class=\"web2-card\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var6 string
|
templ_7745c5c3_Err = core.SectionHead("Available Exports", fmt.Sprintf("%d files", len(entries))).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><a class=\"web2-button\" href=\"/\">Back to Dashboard</a></div></section><section class=\"web2-card\"><div class=\"flex items-center justify-between gap-3 mb-4 flex-wrap\"><h2 class=\"text-lg font-semibold\">Available Exports</h2><span class=\"web2-badge\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"web2-table-shell\"><table class=\"web2-table\"><thead><tr><th>Snapshot</th><th>Records</th><th class=\"text-right\">Download</th></tr></thead> <tbody>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " files</span></div><div class=\"overflow-hidden border border-slate-200 rounded\"><table class=\"web2-table\"><thead><tr><th>Snapshot</th><th>Records</th><th class=\"text-right\">Download</th></tr></thead> <tbody>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
if entry.Group != "" && (i == 0 || entries[i-1].Group != entry.Group) {
|
if entry.Group != "" && (i == 0 || entries[i-1].Group != entry.Group) {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<tr class=\"web2-group-row\"><td colspan=\"3\" class=\"font-semibold text-slate-700\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<tr class=\"web2-group-row\"><td colspan=\"3\" class=\"font-semibold\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var8 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Group)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Group)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `views/snapshots.templ`, Line: 88, Col: 62}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</td></tr>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " <tr><td><div class=\"flex flex-col\"><span class=\"text-sm font-semibold\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Label)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 94, Col: 61}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span></div></td><td><span class=\"web2-badge\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Count)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 98, Col: 49}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " records</span></td><td class=\"text-right\"><a class=\"web2-link\" href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(entry.Link)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 101, Col: 49}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</td></tr>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">Download XLSX</a></td></tr>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <tr><td><div class=\"flex flex-col\"><span class=\"text-sm font-semibold text-slate-700\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</tbody></table></div></section></main></body>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span></div></td><td><span class=\"web2-badge\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " records</span></td><td class=\"text-right\"><a class=\"web2-link\" href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">Download XLSX</a></td></tr>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</tbody></table></div></section></main></body>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -274,7 +265,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -298,12 +289,12 @@ func VcenterList(links []VcenterLink) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var12 == nil {
|
if templ_7745c5c3_Var9 == nil {
|
||||||
templ_7745c5c3_Var12 = templ.NopComponent
|
templ_7745c5c3_Var9 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<!doctype html><html lang=\"en\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<!doctype html><html lang=\"en\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -311,34 +302,48 @@ func VcenterList(links []VcenterLink) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell space-y-8\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">vCenter Inventory</div><h1 class=\"mt-3 text-4xl font-bold\">Monitored vCenters</h1><p class=\"mt-2 text-sm text-slate-600\">Select a vCenter to view snapshot totals over time.</p></div><a class=\"web2-button\" href=\"/\">Back to Dashboard</a></div></section><section class=\"web2-card\"><div class=\"flex items-center justify-between gap-3 mb-4 flex-wrap\"><h2 class=\"text-lg font-semibold\">vCenters</h2><span class=\"web2-badge\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell web2-card-grid\"><section class=\"web2-header web2-page-head\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var13 string
|
templ_7745c5c3_Err = core.PageHeader(
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(len(links))
|
"vCenter Inventory",
|
||||||
if templ_7745c5c3_Err != nil {
|
"Monitored vCenters",
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 130, Col: 43}
|
"Select a vCenter to view snapshot totals over time.",
|
||||||
}
|
[]core.ActionLink{
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
{
|
||||||
|
Label: "Back to Dashboard",
|
||||||
|
Href: "/",
|
||||||
|
Class: "web2-button secondary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " total</span></div><div class=\"overflow-hidden border border-slate-200 rounded\"><table class=\"web2-table\"><thead><tr><th>vCenter</th><th class=\"text-right\">Totals</th></tr></thead> <tbody>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</section><section class=\"web2-card\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.SectionHead("vCenters", fmt.Sprintf("%d total", len(links))).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"web2-table-shell\"><table class=\"web2-table\"><thead><tr><th>vCenter</th><th class=\"text-right\">Totals</th></tr></thead> <tbody>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<tr><td class=\"font-semibold text-slate-700\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<tr><td class=\"font-semibold\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var14 string
|
var templ_7745c5c3_Var10 string
|
||||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(link.Name)
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(link.Name)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `views/snapshots.templ`, Line: 148, Col: 47}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -346,12 +351,12 @@ func VcenterList(links []VcenterLink) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var15 templ.SafeURL
|
var templ_7745c5c3_Var11 templ.SafeURL
|
||||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinURLErrs(link.Link)
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(link.Link)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `views/snapshots.templ`, Line: 150, Col: 48}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -392,9 +397,9 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var16 == nil {
|
if templ_7745c5c3_Var12 == nil {
|
||||||
templ_7745c5c3_Var16 = templ.NopComponent
|
templ_7745c5c3_Var12 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<!doctype html><html lang=\"en\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<!doctype html><html lang=\"en\">")
|
||||||
@@ -405,214 +410,137 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell web2-shell-wide space-y-8 max-w-screen-2xl mx-auto\" style=\"max-width: 1400px; width: 100%;\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">vCenter Totals</div><h1 class=\"mt-3 text-4xl font-bold\">Totals for ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell web2-shell-wide web2-card-grid\"><section class=\"web2-header web2-page-head\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.PageHeader(
|
||||||
|
"vCenter Totals",
|
||||||
|
"Totals for "+vcenter,
|
||||||
|
meta.TypeLabel+" snapshots of VM count, vCPU, and RAM over time.",
|
||||||
|
[]core.ActionLink{
|
||||||
|
{
|
||||||
|
Label: "All vCenters",
|
||||||
|
Href: "/vcenters",
|
||||||
|
Class: "web2-button secondary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Label: "Dashboard",
|
||||||
|
Href: "/",
|
||||||
|
Class: "web2-button",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.SegmentedActions(
|
||||||
|
[]core.SegmentedLink{
|
||||||
|
{
|
||||||
|
Label: "Hourly Detail (45d)",
|
||||||
|
Href: meta.HourlyLink,
|
||||||
|
Class: meta.HourlyClass,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Label: "Daily Aggregated",
|
||||||
|
Href: meta.DailyLink,
|
||||||
|
Class: meta.DailyClass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</section><section class=\"web2-card\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.SectionHead(meta.TypeLabel+" Snapshots", fmt.Sprintf("%d records", len(entries))).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if chart.ConfigJSON != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"mb-6 overflow-auto\"><div class=\"web3-chart-frame\"><canvas id=\"vcenter-totals-chart\" class=\"web3-chart-canvas\" role=\"img\" aria-label=\"Totals over time\" data-chart-config=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(chart.ConfigJSON)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 208, Col: 145}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\"></canvas><div id=\"vcenter-totals-tooltip\" class=\"web3-chart-tooltip\" aria-hidden=\"true\"></div></div><script>\n\t\t\t\t\t\t\t\twindow.Web3Charts.renderFromDataset({\n\t\t\t\t\t\t\t\t\tcanvasId: \"vcenter-totals-chart\",\n\t\t\t\t\t\t\t\t\ttooltipId: \"vcenter-totals-tooltip\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t</script></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<div class=\"web2-table-shell\"><table class=\"web2-table\"><thead><tr><th>Snapshot Time</th><th class=\"text-right\">VMs</th><th class=\"text-right\">vCPUs</th><th class=\"text-right\">RAM (GB)</th></tr></thead> <tbody>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<tr><td>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var14 string
|
||||||
|
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Snapshot)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 232, Col: 30}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</td><td class=\"text-right\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var15 string
|
||||||
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(entry.VmCount)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 233, Col: 48}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</td><td class=\"text-right\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var16 string
|
||||||
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(entry.VcpuTotal)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 234, Col: 50}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</td><td class=\"text-right\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var17 string
|
var templ_7745c5c3_Var17 string
|
||||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(vcenter)
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(entry.RamTotalGB)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `views/snapshots.templ`, Line: 235, Col: 51}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</h1><p class=\"mt-2 text-sm text-slate-600\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</td></tr>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 25, " snapshots of VM count, vCPU, and RAM over time.</p></div><div class=\"flex gap-3\"><a class=\"web2-button secondary\" href=\"/vcenters\">All vCenters</a> <a class=\"web2-button\" href=\"/\">Dashboard</a></div></div><div class=\"web3-button-group mt-8 mb-3\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var19 = []any{meta.HourlyClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, "\" href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 28, "\">Hourly Detail (45d)</a> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var22 = []any{meta.DailyClass}
|
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<a class=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 30, "\" href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 31, "\">Daily Aggregated</a></div></section><section class=\"web2-card\"><div class=\"flex items-center justify-between gap-3 mb-4 flex-wrap\"><h2 class=\"text-lg font-semibold\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 32, " Snapshots</h2><span class=\"web2-badge\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 33, " records</span></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if chart.ConfigJSON != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div class=\"mb-6 overflow-auto\"><div class=\"web3-chart-frame\"><canvas id=\"vcenter-totals-chart\" class=\"web3-chart-canvas\" role=\"img\" aria-label=\"Totals over time\" data-chart-config=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, 35, "\"></canvas><div id=\"vcenter-totals-tooltip\" class=\"web3-chart-tooltip\" aria-hidden=\"true\"></div></div><script>\n\t\t\t\t\t\t\t\twindow.Web3Charts.renderFromDataset({\n\t\t\t\t\t\t\t\t\tcanvasId: \"vcenter-totals-chart\",\n\t\t\t\t\t\t\t\t\ttooltipId: \"vcenter-totals-tooltip\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t</script></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<div class=\"overflow-hidden border border-slate-200 rounded\"><table class=\"web2-table\"><thead><tr><th>Snapshot Time</th><th class=\"text-right\">VMs</th><th class=\"text-right\">vCPUs</th><th class=\"text-right\">RAM (GB)</th></tr></thead> <tbody>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</tbody></table></div></section></main></body>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<tr><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, "</td><td class=\"text-right\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, 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, "</td><td class=\"text-right\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</td><td class=\"text-right\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</td></tr>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</tbody></table></div></section></main></body>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -620,7 +548,7 @@ func VcenterTotalsPage(vcenter string, entries []VcenterTotalsEntry, chart Vcent
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,48 +48,56 @@ templ VmTracePage(query string, display_query string, vm_id string, vm_uuid stri
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
@core.Header()
|
@core.Header()
|
||||||
<body class="flex flex-col min-h-screen web2-bg">
|
<body class="flex flex-col min-h-screen web2-bg">
|
||||||
<main class="flex-grow web2-shell web2-shell-wide space-y-8 max-w-screen-2xl mx-auto">
|
<main class="flex-grow web2-shell web2-shell-wide web2-card-grid">
|
||||||
<section class="web2-header">
|
<section class="web2-header web2-page-head">
|
||||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
@core.PageHeader(
|
||||||
<div>
|
"VM Trace",
|
||||||
<div class="web2-pill">VM Trace</div>
|
"Snapshot history"+display_query,
|
||||||
<h1 class="mt-3 text-4xl font-bold">Snapshot history{display_query}</h1>
|
"Timeline of vCPU, RAM, and resource pool changes across "+meta.TypeLabel+" snapshots.",
|
||||||
<p class="mt-2 text-sm text-slate-600">Timeline of vCPU, RAM, and resource pool changes across { meta.TypeLabel } snapshots.</p>
|
[]core.ActionLink{
|
||||||
</div>
|
{
|
||||||
<div class="flex gap-3 flex-wrap">
|
Label: "Dashboard",
|
||||||
<a class="web2-button" href="/">Dashboard</a>
|
Href: "/",
|
||||||
</div>
|
Class: "web2-button secondary",
|
||||||
</div>
|
},
|
||||||
<form method="get" action="/vm/trace" class="mt-4 grid gap-3 md:grid-cols-3">
|
},
|
||||||
|
)
|
||||||
|
<form method="get" action="/vm/trace" class="web2-form-grid">
|
||||||
<input type="hidden" name="view" value={ meta.ViewType }/>
|
<input type="hidden" name="view" value={ meta.ViewType }/>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="web2-field">
|
||||||
<label class="text-sm text-slate-600" for="vm_id">VM ID</label>
|
<label class="web2-label" for="vm_id">VM ID</label>
|
||||||
<input class="web2-card border border-slate-200 px-3 py-2 rounded" type="text" id="vm_id" name="vm_id" value={vm_id} placeholder="vm-12345"/>
|
<input class="web2-input" type="text" id="vm_id" name="vm_id" value={ vm_id } placeholder="vm-12345"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="web2-field">
|
||||||
<label class="text-sm text-slate-600" for="vm_uuid">VM UUID</label>
|
<label class="web2-label" for="vm_uuid">VM UUID</label>
|
||||||
<input class="web2-card border border-slate-200 px-3 py-2 rounded" type="text" id="vm_uuid" name="vm_uuid" value={vm_uuid} placeholder="uuid..."/>
|
<input class="web2-input" type="text" id="vm_uuid" name="vm_uuid" value={ vm_uuid } placeholder="uuid..."/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="web2-field">
|
||||||
<label class="text-sm text-slate-600" for="name">Name</label>
|
<label class="web2-label" for="name">Name</label>
|
||||||
<input class="web2-card border border-slate-200 px-3 py-2 rounded" type="text" id="name" name="name" value={vm_name} placeholder="VM name"/>
|
<input class="web2-input" type="text" id="name" name="name" value={ vm_name } placeholder="VM name"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-3 flex gap-2">
|
<div class="web2-form-actions web2-form-actions-full">
|
||||||
<button class="web3-button active" type="submit">Load VM Trace</button>
|
<button class="web3-button active" type="submit">Load VM Trace</button>
|
||||||
<a class="web3-button" href="/vm/trace">Clear</a>
|
<a class="web3-button" href="/vm/trace">Clear</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="web3-button-group mt-5 mb-1">
|
@core.SegmentedActions(
|
||||||
<a class={ meta.HourlyClass } href={ meta.HourlyLink }>Hourly Detail</a>
|
[]core.SegmentedLink{
|
||||||
<a class={ meta.DailyClass } href={ meta.DailyLink }>Daily Aggregated</a>
|
{
|
||||||
</div>
|
Label: "Hourly Detail",
|
||||||
|
Href: meta.HourlyLink,
|
||||||
|
Class: meta.HourlyClass,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Label: "Daily Aggregated",
|
||||||
|
Href: meta.DailyLink,
|
||||||
|
Class: meta.DailyClass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web2-card">
|
<section class="web2-card">
|
||||||
<div class="flex items-center justify-between gap-3 mb-4 flex-wrap">
|
@core.SectionHead(meta.TypeLabel+" Timeline", fmt.Sprintf("%d samples", len(entries)))
|
||||||
<h2 class="text-lg font-semibold">{ meta.TypeLabel } Timeline</h2>
|
|
||||||
<span class="web2-badge">{len(entries)} samples</span>
|
|
||||||
</div>
|
|
||||||
if chart.ConfigJSON != "" {
|
if chart.ConfigJSON != "" {
|
||||||
<div class="mb-6 overflow-auto">
|
<div class="mb-6 overflow-auto">
|
||||||
<div class="web3-chart-frame">
|
<div class="web3-chart-frame">
|
||||||
@@ -105,28 +113,28 @@ templ VmTracePage(query string, display_query string, vm_id string, vm_uuid stri
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="grid gap-3 md:grid-cols-2 mb-4">
|
<div class="grid gap-3 md:grid-cols-2 mb-4">
|
||||||
<div class="web2-card">
|
<div class="web2-subcard">
|
||||||
<p class="text-xs uppercase tracking-[0.15em] text-slate-500">Creation time</p>
|
<p class="web2-subcard-label">Creation Time</p>
|
||||||
<p class="mt-2 text-base font-semibold text-slate-800">{creationLabel}</p>
|
<p class="web2-subcard-value">{ creationLabel }</p>
|
||||||
if creationApprox {
|
if creationApprox {
|
||||||
<p class="text-xs text-slate-500 mt-1">Approximate (earliest snapshot)</p>
|
<p class="web2-muted web2-caption mt-1">Approximate (earliest snapshot)</p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="web2-card">
|
<div class="web2-subcard">
|
||||||
<p class="text-xs uppercase tracking-[0.15em] text-slate-500">Deletion time</p>
|
<p class="web2-subcard-label">Deletion Time</p>
|
||||||
<p class="mt-2 text-base font-semibold text-slate-800">{deletionLabel}</p>
|
<p class="web2-subcard-value">{ deletionLabel }</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
if diagnostics.Visible && len(diagnostics.Lines) > 0 {
|
if diagnostics.Visible && len(diagnostics.Lines) > 0 {
|
||||||
<details class="web2-card mb-4">
|
<details class="web2-subcard mb-4">
|
||||||
<summary class="cursor-pointer text-sm font-semibold text-slate-700">Lifecycle diagnostics</summary>
|
<summary class="web2-details-summary">Lifecycle diagnostics</summary>
|
||||||
<div class="mt-3 overflow-hidden border border-slate-200 rounded">
|
<div class="mt-3 web2-table-shell">
|
||||||
<table class="web2-table">
|
<table class="web2-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
for _, line := range diagnostics.Lines {
|
for _, line := range diagnostics.Lines {
|
||||||
<tr>
|
<tr>
|
||||||
<td class="font-semibold text-slate-700 w-72">{ line.Label }</td>
|
<td class="font-semibold w-72">{ line.Label }</td>
|
||||||
<td class="text-slate-600">{ line.Value }</td>
|
<td class="web2-muted">{ line.Value }</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -134,7 +142,7 @@ templ VmTracePage(query string, display_query string, vm_id string, vm_uuid stri
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
}
|
}
|
||||||
<div class="overflow-hidden border border-slate-200 rounded">
|
<div class="web2-table-shell">
|
||||||
<table class="web2-table">
|
<table class="web2-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
+157
-235
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.3.977
|
// templ: version: v0.3.1001
|
||||||
package views
|
package views
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
@@ -80,413 +80,335 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell web2-shell-wide space-y-8 max-w-screen-2xl mx-auto\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">VM Trace</div><h1 class=\"mt-3 text-4xl font-bold\">Snapshot history")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell web2-shell-wide web2-card-grid\"><section class=\"web2-header web2-page-head\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.PageHeader(
|
||||||
|
"VM Trace",
|
||||||
|
"Snapshot history"+display_query,
|
||||||
|
"Timeline of vCPU, RAM, and resource pool changes across "+meta.TypeLabel+" snapshots.",
|
||||||
|
[]core.ActionLink{
|
||||||
|
{
|
||||||
|
Label: "Dashboard",
|
||||||
|
Href: "/",
|
||||||
|
Class: "web2-button secondary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<form method=\"get\" action=\"/vm/trace\" class=\"web2-form-grid\"><input type=\"hidden\" name=\"view\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(display_query)
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(meta.ViewType)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 56, Col: 74}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 66, Col: 60}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-sm text-slate-600\">Timeline of vCPU, RAM, and resource pool changes across ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><div class=\"web2-field\"><label class=\"web2-label\" for=\"vm_id\">VM ID</label> <input class=\"web2-input\" type=\"text\" id=\"vm_id\" name=\"vm_id\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var3 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(meta.TypeLabel)
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(vm_id)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 57, Col: 119}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 69, Col: 82}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " snapshots.</p></div><div class=\"flex gap-3 flex-wrap\"><a class=\"web2-button\" href=\"/\">Dashboard</a></div></div><form method=\"get\" action=\"/vm/trace\" class=\"mt-4 grid gap-3 md:grid-cols-3\"><input type=\"hidden\" name=\"view\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" placeholder=\"vm-12345\"></div><div class=\"web2-field\"><label class=\"web2-label\" for=\"vm_uuid\">VM UUID</label> <input class=\"web2-input\" type=\"text\" id=\"vm_uuid\" name=\"vm_uuid\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var4 string
|
var templ_7745c5c3_Var4 string
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(meta.ViewType)
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(vm_uuid)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 64, Col: 61}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 73, Col: 88}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><div class=\"flex flex-col gap-1\"><label class=\"text-sm text-slate-600\" for=\"vm_id\">VM ID</label> <input class=\"web2-card border border-slate-200 px-3 py-2 rounded\" type=\"text\" id=\"vm_id\" name=\"vm_id\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" placeholder=\"uuid...\"></div><div class=\"web2-field\"><label class=\"web2-label\" for=\"name\">Name</label> <input class=\"web2-input\" type=\"text\" id=\"name\" name=\"name\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var5 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(vm_id)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(vm_name)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 67, Col: 123}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 77, Col: 82}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" placeholder=\"vm-12345\"></div><div class=\"flex flex-col gap-1\"><label class=\"text-sm text-slate-600\" for=\"vm_uuid\">VM UUID</label> <input class=\"web2-card border border-slate-200 px-3 py-2 rounded\" type=\"text\" id=\"vm_uuid\" name=\"vm_uuid\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" placeholder=\"VM name\"></div><div class=\"web2-form-actions web2-form-actions-full\"><button class=\"web3-button active\" type=\"submit\">Load VM Trace</button> <a class=\"web3-button\" href=\"/vm/trace\">Clear</a></div></form>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.SegmentedActions(
|
||||||
|
[]core.SegmentedLink{
|
||||||
|
{
|
||||||
|
Label: "Hourly Detail",
|
||||||
|
Href: meta.HourlyLink,
|
||||||
|
Class: meta.HourlyClass,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Label: "Daily Aggregated",
|
||||||
|
Href: meta.DailyLink,
|
||||||
|
Class: meta.DailyClass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</section><section class=\"web2-card\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = core.SectionHead(meta.TypeLabel+" Timeline", fmt.Sprintf("%d samples", len(entries))).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if chart.ConfigJSON != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"mb-6 overflow-auto\"><div class=\"web3-chart-frame\"><canvas id=\"vm-trace-chart\" class=\"web3-chart-canvas\" role=\"img\" aria-label=\"VM timeline\" data-chart-config=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(vm_uuid)
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(chart.ConfigJSON)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 71, Col: 129}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 104, Col: 134}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" placeholder=\"uuid...\"></div><div class=\"flex flex-col gap-1\"><label class=\"text-sm text-slate-600\" for=\"name\">Name</label> <input class=\"web2-card border border-slate-200 px-3 py-2 rounded\" type=\"text\" id=\"name\" name=\"name\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"></canvas><div id=\"vm-trace-tooltip\" class=\"web3-chart-tooltip\" aria-hidden=\"true\"></div></div><script>\n\t\t\t\t\t\t\t\twindow.Web3Charts.renderFromDataset({\n\t\t\t\t\t\t\t\t\tcanvasId: \"vm-trace-chart\",\n\t\t\t\t\t\t\t\t\ttooltipId: \"vm-trace-tooltip\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t</script></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"grid gap-3 md:grid-cols-2 mb-4\"><div class=\"web2-subcard\"><p class=\"web2-subcard-label\">Creation Time</p><p class=\"web2-subcard-value\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var7 string
|
var templ_7745c5c3_Var7 string
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(vm_name)
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(creationLabel)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 75, Col: 123}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 118, Col: 52}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" placeholder=\"VM name\"></div><div class=\"md:col-span-3 flex gap-2\"><button class=\"web3-button active\" type=\"submit\">Load VM Trace</button> <a class=\"web3-button\" href=\"/vm/trace\">Clear</a></div></form><div class=\"web3-button-group mt-5 mb-1\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var8 = []any{meta.HourlyClass}
|
if creationApprox {
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<p class=\"web2-muted web2-caption mt-1\">Approximate (earliest snapshot)</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<a class=\"")
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div><div class=\"web2-subcard\"><p class=\"web2-subcard-label\">Deletion Time</p><p class=\"web2-subcard-value\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(deletionLabel)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 125, Col: 52}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</p></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if diagnostics.Visible && len(diagnostics.Lines) > 0 {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<details class=\"web2-subcard mb-4\"><summary class=\"web2-details-summary\">Lifecycle diagnostics</summary><div class=\"mt-3 web2-table-shell\"><table class=\"web2-table\"><tbody>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, line := range diagnostics.Lines {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<tr><td class=\"font-semibold w-72\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var9 string
|
var templ_7745c5c3_Var9 string
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String())
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(line.Label)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `views/vm_trace.templ`, Line: 136, Col: 55}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td><td class=\"web2-muted\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var10 templ.SafeURL
|
var templ_7745c5c3_Var10 string
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(meta.HourlyLink)
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(line.Value)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 83, Col: 59}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 137, Col: 47}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">Hourly Detail</a> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td></tr>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var11 = []any{meta.DailyClass}
|
}
|
||||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</tbody></table></div></details>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<a class=\"")
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"web2-table-shell\"><table class=\"web2-table\"><thead><tr><th>Snapshot</th><th>VM Name</th><th>VmId</th><th>VmUuid</th><th>Vcenter</th><th>Resource Pool</th><th class=\"text-right\">vCPUs</th><th class=\"text-right\">RAM (GB)</th><th class=\"text-right\">Disk</th></tr></thead> <tbody>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<tr><td>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var11 string
|
||||||
|
templ_7745c5c3_Var11, 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: 163, Col: 26}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</td><td>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var12 string
|
var templ_7745c5c3_Var12 string
|
||||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
|
||||||
if templ_7745c5c3_Err != nil {
|
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: `views/vm_trace.templ`, Line: 164, Col: 22}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td><td>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var13 templ.SafeURL
|
var templ_7745c5c3_Var13 string
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(meta.DailyLink)
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(e.VmId)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 84, Col: 57}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 165, Col: 22}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">Daily Aggregated</a></div></section><section class=\"web2-card\"><div class=\"flex items-center justify-between gap-3 mb-4 flex-wrap\"><h2 class=\"text-lg font-semibold\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</td><td>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var14 string
|
var templ_7745c5c3_Var14 string
|
||||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(meta.TypeLabel)
|
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(e.VmUuid)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 90, Col: 56}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 166, Col: 24}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " Timeline</h2><span class=\"web2-badge\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td><td>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var15 string
|
var templ_7745c5c3_Var15 string
|
||||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(len(entries))
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(e.Vcenter)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 91, Col: 44}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 167, Col: 25}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " samples</span></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</td><td>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if chart.ConfigJSON != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"mb-6 overflow-auto\"><div class=\"web3-chart-frame\"><canvas id=\"vm-trace-chart\" class=\"web3-chart-canvas\" role=\"img\" aria-label=\"VM timeline\" data-chart-config=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var16 string
|
var templ_7745c5c3_Var16 string
|
||||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(chart.ConfigJSON)
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(e.ResourcePool)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 96, Col: 133}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 168, Col: 30}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"></canvas><div id=\"vm-trace-tooltip\" class=\"web3-chart-tooltip\" aria-hidden=\"true\"></div></div><script>\n\t\t\t\t\t\t\t\twindow.Web3Charts.renderFromDataset({\n\t\t\t\t\t\t\t\t\tcanvasId: \"vm-trace-chart\",\n\t\t\t\t\t\t\t\t\ttooltipId: \"vm-trace-tooltip\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t</script></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</td><td class=\"text-right\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"grid gap-3 md:grid-cols-2 mb-4\"><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.15em] text-slate-500\">Creation time</p><p class=\"mt-2 text-base font-semibold text-slate-800\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var17 string
|
var templ_7745c5c3_Var17 string
|
||||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(creationLabel)
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(e.VcpuCount)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 110, Col: 76}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 169, Col: 46}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</td><td class=\"text-right\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if creationApprox {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<p class=\"text-xs text-slate-500 mt-1\">Approximate (earliest snapshot)</p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.15em] text-slate-500\">Deletion time</p><p class=\"mt-2 text-base font-semibold text-slate-800\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var18 string
|
var templ_7745c5c3_Var18 string
|
||||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(deletionLabel)
|
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(e.RamGB)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 117, Col: 76}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 170, Col: 42}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</p></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</td><td class=\"text-right\">")
|
||||||
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, "<details class=\"web2-card mb-4\"><summary class=\"cursor-pointer text-sm font-semibold text-slate-700\">Lifecycle diagnostics</summary><div class=\"mt-3 overflow-hidden border border-slate-200 rounded\"><table class=\"web2-table\"><tbody>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, line := range diagnostics.Lines {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<tr><td class=\"font-semibold text-slate-700 w-72\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var19 string
|
var templ_7745c5c3_Var19 string
|
||||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(line.Label)
|
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", e.ProvisionedDisk))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 128, Col: 70}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/vm_trace.templ`, Line: 171, Col: 73}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td><td class=\"text-slate-600\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</td></tr>")
|
||||||
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: `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, "</td></tr>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</tbody></table></div></details>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</tbody></table></div></section></main></body>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"overflow-hidden border border-slate-200 rounded\"><table class=\"web2-table\"><thead><tr><th>Snapshot</th><th>VM Name</th><th>VmId</th><th>VmUuid</th><th>Vcenter</th><th>Resource Pool</th><th class=\"text-right\">vCPUs</th><th class=\"text-right\">RAM (GB)</th><th class=\"text-right\">Disk</th></tr></thead> <tbody>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, e := range entries {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<tr><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var21 string
|
|
||||||
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: 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, 31, "</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var22 string
|
|
||||||
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: 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, 32, "</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var23 string
|
|
||||||
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: 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, 33, "</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var24 string
|
|
||||||
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: 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, 34, "</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var25 string
|
|
||||||
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: 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, 35, "</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var26 string
|
|
||||||
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: 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, 36, "</td><td class=\"text-right\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var27 string
|
|
||||||
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: 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, 37, "</td><td class=\"text-right\">")
|
|
||||||
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: `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, "</td><td class=\"text-right\">")
|
|
||||||
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: `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, "</td></tr>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</tbody></table></div></section></main></body>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -494,7 +416,7 @@ func VmTracePage(query string, display_query string, vm_id string, vm_uuid strin
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Design System Inspired by Airtable
|
||||||
|
|
||||||
|
## 1. Visual Theme & Atmosphere
|
||||||
|
|
||||||
|
Airtable's website is a clean, enterprise-friendly platform that communicates "sophisticated simplicity" through a white canvas with deep navy text (`#181d26`) and Airtable Blue (`#1b61c9`) as the primary interactive accent. The Haas font family (display + text variants) creates a Swiss-precision typography system with positive letter-spacing throughout.
|
||||||
|
|
||||||
|
**Key Characteristics:**
|
||||||
|
- White canvas with deep navy text (`#181d26`)
|
||||||
|
- Airtable Blue (`#1b61c9`) as primary CTA and link color
|
||||||
|
- Haas + Haas Groot Disp dual font system
|
||||||
|
- Positive letter-spacing on body text (0.08px–0.28px)
|
||||||
|
- 12px radius buttons, 16px–32px for cards
|
||||||
|
- Multi-layer blue-tinted shadow: `rgba(45,127,249,0.28) 0px 1px 3px`
|
||||||
|
- Semantic theme tokens: `--theme_*` CSS variable naming
|
||||||
|
|
||||||
|
## 2. Color Palette & Roles
|
||||||
|
|
||||||
|
### Primary
|
||||||
|
- **Deep Navy** (`#181d26`): Primary text
|
||||||
|
- **Airtable Blue** (`#1b61c9`): CTA buttons, links
|
||||||
|
- **White** (`#ffffff`): Primary surface
|
||||||
|
- **Spotlight** (`rgba(249,252,255,0.97)`): `--theme_button-text-spotlight`
|
||||||
|
|
||||||
|
### Semantic
|
||||||
|
- **Success Green** (`#006400`): `--theme_success-text`
|
||||||
|
- **Weak Text** (`rgba(4,14,32,0.69)`): `--theme_text-weak`
|
||||||
|
- **Secondary Active** (`rgba(7,12,20,0.82)`): `--theme_button-text-secondary-active`
|
||||||
|
|
||||||
|
### Neutral
|
||||||
|
- **Dark Gray** (`#333333`): Secondary text
|
||||||
|
- **Mid Blue** (`#254fad`): Link/accent blue variant
|
||||||
|
- **Border** (`#e0e2e6`): Card borders
|
||||||
|
- **Light Surface** (`#f8fafc`): Subtle surface
|
||||||
|
|
||||||
|
### Shadows
|
||||||
|
- **Blue-tinted** (`rgba(0,0,0,0.32) 0px 0px 1px, rgba(0,0,0,0.08) 0px 0px 2px, rgba(45,127,249,0.28) 0px 1px 3px, rgba(0,0,0,0.06) 0px 0px 0px 0.5px inset`)
|
||||||
|
- **Soft** (`rgba(15,48,106,0.05) 0px 0px 20px`)
|
||||||
|
|
||||||
|
## 3. Typography Rules
|
||||||
|
|
||||||
|
### Font Families
|
||||||
|
- **Primary**: `Haas`, fallbacks: `-apple-system, system-ui, Segoe UI, Roboto`
|
||||||
|
- **Display**: `Haas Groot Disp`, fallback: `Haas`
|
||||||
|
|
||||||
|
### Hierarchy
|
||||||
|
|
||||||
|
| Role | Font | Size | Weight | Line Height | Letter Spacing |
|
||||||
|
|------|------|------|--------|-------------|----------------|
|
||||||
|
| Display Hero | Haas | 48px | 400 | 1.15 | normal |
|
||||||
|
| Display Bold | Haas Groot Disp | 48px | 900 | 1.50 | normal |
|
||||||
|
| Section Heading | Haas | 40px | 400 | 1.25 | normal |
|
||||||
|
| Sub-heading | Haas | 32px | 400–500 | 1.15–1.25 | normal |
|
||||||
|
| Card Title | Haas | 24px | 400 | 1.20–1.30 | 0.12px |
|
||||||
|
| Feature | Haas | 20px | 400 | 1.25–1.50 | 0.1px |
|
||||||
|
| Body | Haas | 18px | 400 | 1.35 | 0.18px |
|
||||||
|
| Body Medium | Haas | 16px | 500 | 1.30 | 0.08–0.16px |
|
||||||
|
| Button | Haas | 16px | 500 | 1.25–1.30 | 0.08px |
|
||||||
|
| Caption | Haas | 14px | 400–500 | 1.25–1.35 | 0.07–0.28px |
|
||||||
|
|
||||||
|
## 4. Component Stylings
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
- **Primary Blue**: `#1b61c9`, white text, 16px 24px padding, 12px radius
|
||||||
|
- **White**: white bg, `#181d26` text, 12px radius, 1px border white
|
||||||
|
- **Cookie Consent**: `#1b61c9` bg, 2px radius (sharp)
|
||||||
|
|
||||||
|
### Cards: `1px solid #e0e2e6`, 16px–24px radius
|
||||||
|
### Inputs: Standard Haas styling
|
||||||
|
|
||||||
|
## 5. Layout
|
||||||
|
- Spacing: 1–48px (8px base)
|
||||||
|
- Radius: 2px (small), 12px (buttons), 16px (cards), 24px (sections), 32px (large), 50% (circles)
|
||||||
|
|
||||||
|
## 6. Depth
|
||||||
|
- Blue-tinted multi-layer shadow system
|
||||||
|
- Soft ambient: `rgba(15,48,106,0.05) 0px 0px 20px`
|
||||||
|
|
||||||
|
## 7. Do's and Don'ts
|
||||||
|
### Do: Use Airtable Blue for CTAs, Haas with positive tracking, 12px radius buttons
|
||||||
|
### Don't: Skip positive letter-spacing, use heavy shadows
|
||||||
|
|
||||||
|
## 8. Responsive Behavior
|
||||||
|
Breakpoints: 425–1664px (23 breakpoints)
|
||||||
|
|
||||||
|
## 9. Agent Prompt Guide
|
||||||
|
- Text: Deep Navy (`#181d26`)
|
||||||
|
- CTA: Airtable Blue (`#1b61c9`)
|
||||||
|
- Background: White (`#ffffff`)
|
||||||
|
- Border: `#e0e2e6`
|
||||||
Vendored
+458
-91
@@ -1,45 +1,182 @@
|
|||||||
:root {
|
:root {
|
||||||
--web2-blue: #1d9bf0;
|
--theme_text_primary: #181d26;
|
||||||
--web2-slate: #0f172a;
|
--theme_text_weak: rgba(4, 14, 32, 0.69);
|
||||||
--web2-muted: #64748b;
|
--theme_text_inverse: rgba(249, 252, 255, 0.97);
|
||||||
--web2-card: #ffffff;
|
--theme_text_secondary_active: rgba(7, 12, 20, 0.82);
|
||||||
--web2-border: #e5e7eb;
|
--theme_accent_blue: #1b61c9;
|
||||||
|
--theme_accent_blue_hover: #164fa6;
|
||||||
|
--theme_accent_blue_soft: #e8f0fc;
|
||||||
|
--theme_success_text: #006400;
|
||||||
|
--theme_surface_primary: #ffffff;
|
||||||
|
--theme_surface_subtle: #f8fafc;
|
||||||
|
--theme_surface_shell: #fdfefe;
|
||||||
|
--theme_surface_section: #fbfdff;
|
||||||
|
--theme_border: #e0e2e6;
|
||||||
|
--theme_shadow_card: rgba(0, 0, 0, 0.32) 0 0 1px, rgba(0, 0, 0, 0.08) 0 0 2px, rgba(45, 127, 249, 0.28) 0 1px 3px, rgba(0, 0, 0, 0.06) 0 0 0 0.5px inset;
|
||||||
|
--theme_shadow_soft: rgba(15, 48, 106, 0.05) 0 0 20px;
|
||||||
|
--theme_shadow_button: rgba(45, 127, 249, 0.28) 0 1px 3px;
|
||||||
|
--theme_radius_button: 12px;
|
||||||
|
--theme_radius_card: 16px;
|
||||||
|
--theme_radius_section: 24px;
|
||||||
|
--theme_radius_large: 32px;
|
||||||
|
--theme_font_body: "Haas", "Neue Haas Grotesk Text Pro", "Avenir Next", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||||
|
--theme_font_display: "Haas Groot Disp", "Haas", "Neue Haas Grotesk Display Pro", "Avenir Next", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||||
|
--theme_font_code: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--theme_letter_body: 0.12px;
|
||||||
|
--theme_letter_caption: 0.2px;
|
||||||
|
--theme_letter_button: 0.08px;
|
||||||
|
--theme_transition_fast: 150ms ease;
|
||||||
|
--theme_transition_base: 220ms ease;
|
||||||
|
--web2-blue: var(--theme_accent_blue);
|
||||||
|
--web2-slate: var(--theme_text_primary);
|
||||||
|
--web2-muted: var(--theme_text_weak);
|
||||||
|
--web2-card: var(--theme_surface_primary);
|
||||||
|
--web2-border: var(--theme_border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
body {
|
body {
|
||||||
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
margin: 0;
|
||||||
color: var(--web2-slate);
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--theme_font_body);
|
||||||
|
color: var(--theme_text_primary);
|
||||||
|
letter-spacing: var(--theme_letter_body);
|
||||||
|
background: var(--theme_surface_shell);
|
||||||
|
}
|
||||||
|
|
||||||
.web2-bg {
|
.web2-bg {
|
||||||
background: #ffffff;
|
background:
|
||||||
|
radial-gradient(circle at 8% 4%, rgba(27, 97, 201, 0.08) 0, rgba(27, 97, 201, 0) 30%),
|
||||||
|
radial-gradient(circle at 90% 12%, rgba(37, 79, 173, 0.08) 0, rgba(37, 79, 173, 0) 28%),
|
||||||
|
var(--theme_surface_shell);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-shell {
|
.web2-shell {
|
||||||
max-width: 1100px;
|
max-width: 1140px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 1.5rem 4rem;
|
padding: 2.5rem 1.5rem 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-shell-wide {
|
.web2-shell-wide {
|
||||||
max-width: 1400px;
|
max-width: 1420px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.web2-page-head {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-page-head-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-head-copy {
|
||||||
|
max-width: 72ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-page-title {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
font-family: var(--theme_font_display);
|
||||||
|
font-size: clamp(1.95rem, 1.2rem + 1.9vw, 2.65rem);
|
||||||
|
line-height: 1.15;
|
||||||
|
letter-spacing: 0.06px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-page-subtitle {
|
||||||
|
margin-top: 0.45rem;
|
||||||
|
font-size: 0.96rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--theme_text_weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-section-head {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-kpi-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-kpi-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
color: var(--theme_text_weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-kpi-value {
|
||||||
|
margin-top: 0.55rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--theme_text_primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-note {
|
||||||
|
padding: 0.78rem 0.92rem;
|
||||||
|
border-radius: var(--theme_radius_button);
|
||||||
|
border: 1px solid rgba(27, 97, 201, 0.28);
|
||||||
|
background: rgba(27, 97, 201, 0.08);
|
||||||
|
color: var(--theme_text_secondary_active);
|
||||||
|
font-size: 0.89rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
.web2-header {
|
.web2-header {
|
||||||
background: var(--web2-card);
|
background: var(--theme_surface_section);
|
||||||
border: 1px solid var(--web2-border);
|
border: 1px solid var(--theme_border);
|
||||||
border-radius: 4px;
|
border-radius: var(--theme_radius_section);
|
||||||
padding: 1.5rem 2rem;
|
padding: 1.65rem 2rem;
|
||||||
|
box-shadow: var(--theme_shadow_card), var(--theme_shadow_soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-card {
|
.web2-card {
|
||||||
background: var(--web2-card);
|
background: var(--theme_surface_primary);
|
||||||
border: 1px solid var(--web2-border);
|
border: 1px solid var(--theme_border);
|
||||||
border-radius: 4px;
|
border-radius: var(--theme_radius_card);
|
||||||
padding: 1.5rem 1.75rem;
|
padding: 1.4rem 1.6rem;
|
||||||
|
box-shadow: var(--theme_shadow_card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-card h2 {
|
.web2-card h2 {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 0.75rem;
|
padding-left: 0.9rem;
|
||||||
font-size: 1.05rem;
|
font-size: 1.1rem;
|
||||||
font-weight: 700;
|
font-family: var(--theme_font_display);
|
||||||
letter-spacing: 0.02em;
|
font-weight: 600;
|
||||||
color: #0b1220;
|
letter-spacing: var(--theme_letter_button);
|
||||||
|
color: var(--theme_text_primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-card h2::before {
|
.web2-card h2::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -47,191 +184,355 @@ body {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 70%;
|
height: 72%;
|
||||||
background: var(--web2-blue);
|
background: var(--theme_accent_blue);
|
||||||
border-radius: 2px;
|
border-radius: 999px;
|
||||||
box-shadow: 0 0 0 1px rgba(29, 155, 240, 0.18);
|
box-shadow: 0 0 0 1px rgba(27, 97, 201, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-pill {
|
.web2-pill {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
background: #f8fafc;
|
background: var(--theme_surface_subtle);
|
||||||
border: 1px solid var(--web2-border);
|
border: 1px solid var(--theme_border);
|
||||||
color: var(--web2-muted);
|
color: var(--theme_text_weak);
|
||||||
padding: 0.2rem 0.6rem;
|
padding: 0.28rem 0.72rem;
|
||||||
border-radius: 3px;
|
border-radius: var(--theme_radius_button);
|
||||||
font-size: 0.85rem;
|
font-size: 0.82rem;
|
||||||
letter-spacing: 0.02em;
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-code {
|
.web2-code {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: var(--theme_font_code);
|
||||||
background: #f1f5f9;
|
background: rgba(27, 97, 201, 0.07);
|
||||||
border: 1px solid var(--web2-border);
|
border: 1px solid var(--theme_border);
|
||||||
border-radius: 3px;
|
border-radius: 8px;
|
||||||
padding: 0.1rem 0.35rem;
|
padding: 0.12rem 0.42rem;
|
||||||
font-size: 0.85em;
|
font-size: 0.84em;
|
||||||
color: #0f172a;
|
color: var(--theme_text_primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-paragraphs p + p {
|
.web2-paragraphs p + p {
|
||||||
margin-top: 0.85rem;
|
margin-top: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-link {
|
.web2-link {
|
||||||
color: var(--web2-blue);
|
color: var(--theme_accent_blue);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
transition: color var(--theme_transition_fast), text-decoration-color var(--theme_transition_fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-link:hover {
|
.web2-link:hover {
|
||||||
|
color: var(--theme_accent_blue_hover);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-button {
|
.web2-button {
|
||||||
background: var(--web2-blue);
|
background: var(--theme_accent_blue);
|
||||||
color: #fff;
|
color: var(--theme_text_inverse);
|
||||||
padding: 0.45rem 0.9rem;
|
padding: 0.56rem 1rem;
|
||||||
border-radius: 3px;
|
border-radius: var(--theme_radius_button);
|
||||||
border: 1px solid #1482d0;
|
border: 1px solid rgba(22, 79, 166, 0.95);
|
||||||
box-shadow: none;
|
box-shadow: var(--theme_shadow_button);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
letter-spacing: var(--theme_letter_button);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: transform var(--theme_transition_fast), background var(--theme_transition_fast), border-color var(--theme_transition_fast), box-shadow var(--theme_transition_fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-button:hover {
|
.web2-button:hover {
|
||||||
background: #1787d4;
|
background: var(--theme_accent_blue_hover);
|
||||||
|
border-color: var(--theme_accent_blue_hover);
|
||||||
|
box-shadow: rgba(37, 79, 173, 0.32) 0 3px 7px;
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.web2-button.secondary {
|
||||||
|
background: var(--theme_surface_primary);
|
||||||
|
color: var(--theme_text_secondary_active);
|
||||||
|
border-color: var(--theme_border);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-button.secondary:hover {
|
||||||
|
background: var(--theme_surface_subtle);
|
||||||
|
border-color: #c7cdd6;
|
||||||
|
color: var(--theme_text_primary);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.web2-button-group {
|
.web2-button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-button-group .web2-button {
|
.web2-button-group .web2-button {
|
||||||
margin: 0 0.5rem 0.5rem 0;
|
margin: 0 0.5rem 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-button {
|
.web3-button {
|
||||||
background: #f3f4f6;
|
background: var(--theme_surface_subtle);
|
||||||
color: #0f172a;
|
color: var(--theme_text_primary);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.52rem 1.02rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--theme_radius_button);
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--theme_border);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
|
letter-spacing: var(--theme_letter_button);
|
||||||
|
transition: background var(--theme_transition_fast), border-color var(--theme_transition_fast), color var(--theme_transition_fast), box-shadow var(--theme_transition_fast);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-button:hover {
|
.web3-button:hover {
|
||||||
background: #e2e8f0;
|
background: #edf2fa;
|
||||||
border-color: #cbd5e1;
|
border-color: #c8d2e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-button.active {
|
.web3-button.active {
|
||||||
background: #dbeafe;
|
background: var(--theme_accent_blue_soft);
|
||||||
border-color: #93c5fd;
|
border-color: rgba(27, 97, 201, 0.45);
|
||||||
color: #1d4ed8;
|
color: var(--theme_accent_blue);
|
||||||
box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.35);
|
box-shadow: rgba(45, 127, 249, 0.28) 0 0 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-button-group {
|
.web3-button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
.web2-list li {
|
|
||||||
background: #ffffff;
|
.web2-table-shell {
|
||||||
border: 1px solid var(--web2-border);
|
overflow-x: auto;
|
||||||
border-radius: 3px;
|
overflow-y: hidden;
|
||||||
padding: 0.75rem 1rem;
|
border: 1px solid var(--theme_border);
|
||||||
box-shadow: none;
|
border-radius: var(--theme_radius_card);
|
||||||
|
background: var(--theme_surface_primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.web2-list li {
|
||||||
|
background: var(--theme_surface_primary);
|
||||||
|
border: 1px solid var(--theme_border);
|
||||||
|
border-radius: var(--theme_radius_card);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
box-shadow: var(--theme_shadow_card);
|
||||||
|
}
|
||||||
|
|
||||||
.web2-table {
|
.web2-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
|
letter-spacing: 0.08px;
|
||||||
|
min-width: 560px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-table thead th {
|
.web2-table thead th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0.75rem 0.5rem;
|
padding: 0.8rem 0.55rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--web2-muted);
|
color: var(--theme_text_weak);
|
||||||
border-bottom: 1px solid var(--web2-border);
|
letter-spacing: var(--theme_letter_caption);
|
||||||
|
border-bottom: 1px solid var(--theme_border);
|
||||||
|
background: rgba(248, 250, 252, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-table tbody td {
|
.web2-table tbody td {
|
||||||
padding: 0.9rem 0.5rem;
|
padding: 0.92rem 0.55rem;
|
||||||
border-bottom: 1px solid var(--web2-border);
|
border-bottom: 1px solid var(--theme_border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-table tbody tr:nth-child(odd) {
|
.web2-table tbody tr:nth-child(odd) {
|
||||||
background: #f8fafc;
|
background: var(--theme_surface_subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-table tbody tr:nth-child(even) {
|
.web2-table tbody tr:nth-child(even) {
|
||||||
background: #ffffff;
|
background: var(--theme_surface_primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-group-row td {
|
.web2-group-row td {
|
||||||
background: #e8eef5;
|
background: #eaf1fb;
|
||||||
color: #0f172a;
|
color: var(--theme_text_primary);
|
||||||
border-bottom: 1px solid var(--web2-border);
|
border-bottom: 1px solid var(--theme_border);
|
||||||
padding: 0.65rem 0.5rem;
|
padding: 0.7rem 0.55rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web2-badge {
|
.web2-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
border: 1px solid var(--web2-border);
|
border: 1px solid var(--theme_border);
|
||||||
padding: 0.15rem 0.45rem;
|
padding: 0.16rem 0.5rem;
|
||||||
border-radius: 3px;
|
border-radius: var(--theme_radius_button);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: var(--web2-muted);
|
letter-spacing: var(--theme_letter_caption);
|
||||||
background: #f8fafc;
|
color: var(--theme_text_weak);
|
||||||
|
background: var(--theme_surface_subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.web2-form-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-label {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
color: var(--theme_text_weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.62rem 0.72rem;
|
||||||
|
border: 1px solid var(--theme_border);
|
||||||
|
border-radius: var(--theme_radius_button);
|
||||||
|
background: var(--theme_surface_primary);
|
||||||
|
color: var(--theme_text_primary);
|
||||||
|
font: inherit;
|
||||||
|
letter-spacing: var(--theme_letter_body);
|
||||||
|
transition: border-color var(--theme_transition_fast), box-shadow var(--theme_transition_fast), background var(--theme_transition_fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-input::placeholder {
|
||||||
|
color: rgba(4, 14, 32, 0.46);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-input:hover {
|
||||||
|
border-color: #c9d0db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-input:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(27, 97, 201, 0.8);
|
||||||
|
box-shadow: rgba(45, 127, 249, 0.26) 0 0 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-form-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-form-actions-full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-subcard {
|
||||||
|
padding: 0.95rem 1rem;
|
||||||
|
border-radius: var(--theme_radius_card);
|
||||||
|
border: 1px solid var(--theme_border);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0, rgba(248, 250, 252, 0.75) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-subcard-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--theme_text_weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-subcard-value {
|
||||||
|
margin-top: 0.45rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--theme_text_primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-muted {
|
||||||
|
color: var(--theme_text_weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-caption {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: var(--theme_letter_caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-details-summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--theme_text_secondary_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-card-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.web3-chart-frame {
|
.web3-chart-frame {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 760px;
|
min-width: 760px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-canvas {
|
.web3-chart-canvas {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 360px;
|
height: 360px;
|
||||||
background: #ffffff;
|
background: var(--theme_surface_primary);
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid var(--theme_border);
|
||||||
border-radius: 6px;
|
border-radius: var(--theme_radius_card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip {
|
.web3-chart-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background: rgba(15, 23, 42, 0.95);
|
background: rgba(24, 29, 38, 0.97);
|
||||||
color: #f8fafc;
|
color: var(--theme_text_inverse);
|
||||||
padding: 0.55rem 0.65rem;
|
padding: 0.55rem 0.65rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--theme_radius_button);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
min-width: 170px;
|
min-width: 170px;
|
||||||
box-shadow: 0 10px 30px rgba(2, 6, 23, 0.25);
|
box-shadow: 0 10px 30px rgba(2, 6, 23, 0.25);
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
transition: opacity 0.08s linear;
|
transition: opacity 80ms linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip.visible {
|
.web3-chart-tooltip.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip-title {
|
.web3-chart-tooltip-title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip-row {
|
.web3-chart-tooltip-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.65rem;
|
gap: 0.65rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip-label {
|
.web3-chart-tooltip-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #cbd5e1;
|
color: #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip-value {
|
.web3-chart-tooltip-value {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #f8fafc;
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web3-chart-tooltip-swatch {
|
.web3-chart-tooltip-swatch {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
@@ -239,8 +540,74 @@ body {
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
margin-right: 0.35rem;
|
margin-right: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.web2-footer {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.65rem 1.5rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web2-footer-inner {
|
||||||
|
max-width: 1140px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-top: 1px solid var(--theme_border);
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-style: italic;
|
||||||
|
letter-spacing: var(--theme_letter_caption);
|
||||||
|
color: var(--theme_text_weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus-visible,
|
||||||
|
button:focus-visible,
|
||||||
|
summary:focus-visible,
|
||||||
|
.web2-button:focus-visible,
|
||||||
|
.web3-button:focus-visible,
|
||||||
|
.web2-link:focus-visible {
|
||||||
|
outline: 2px solid rgba(27, 97, 201, 0.7);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
|
.web2-shell {
|
||||||
|
padding: 1.5rem 1rem 1.25rem;
|
||||||
|
}
|
||||||
|
.web2-header {
|
||||||
|
border-radius: var(--theme_radius_card);
|
||||||
|
padding: 1.2rem 1rem;
|
||||||
|
}
|
||||||
|
.web2-card {
|
||||||
|
padding: 1.1rem 1rem;
|
||||||
|
}
|
||||||
.web3-chart-frame {
|
.web3-chart-frame {
|
||||||
min-width: 640px;
|
min-width: 640px;
|
||||||
}
|
}
|
||||||
|
.web2-footer {
|
||||||
|
padding: 0.5rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
.web2-page-title {
|
||||||
|
font-size: 1.82rem;
|
||||||
|
}
|
||||||
|
.web2-page-subtitle {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.web2-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.web2-actions .web2-button {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.web2-table {
|
||||||
|
min-width: 520px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 780px) {
|
||||||
|
.web2-kpi-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.web2-form-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,330 @@
|
|||||||
|
# Inventory Capture and Aggregation Optimization Plan
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Optimize for end-to-end runtime with a Postgres-ready design. Keep the current HTTP and report behavior intact, but shift the scheduled data pipeline so it uses canonical append-only/cache tables instead of repeatedly scanning `inventory_hourly_*` tables and regenerating reports inline.
|
||||||
|
|
||||||
|
This plan is intended to be implementation-ready for a `codex-5.3` execution pass.
|
||||||
|
|
||||||
|
Execution-path decision:
|
||||||
|
- For the current architecture and migration phases, scheduled daily and monthly aggregation default to the Go path.
|
||||||
|
- This is a readability-first and current-performance decision, not a claim that Go is inherently faster than a well-designed SQL implementation.
|
||||||
|
- SQL path is retained for compatibility, backfill, and fallback.
|
||||||
|
- SQL remains a future optimization candidate on canonical Postgres tables.
|
||||||
|
- SQL can be promoted to default only after benchmark evidence on canonical Postgres tables shows a clear runtime advantage.
|
||||||
|
|
||||||
|
The target architecture is:
|
||||||
|
1. `vm_hourly_stats` is the canonical hourly fact store.
|
||||||
|
2. `vm_daily_rollup` is the canonical monthly input.
|
||||||
|
3. Per-snapshot tables and XLSX generation remain as compatibility and output concerns, not the primary execution path.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
- Hourly capture already writes both per-snapshot tables and `vm_hourly_stats`.
|
||||||
|
- Daily aggregation has mixed execution paths:
|
||||||
|
- SQL union path over `inventory_hourly_*`
|
||||||
|
- Go path over `vm_hourly_stats` or parallel table scans
|
||||||
|
- Monthly aggregation has mixed execution paths:
|
||||||
|
- SQL path over daily or hourly snapshot tables
|
||||||
|
- Go path over `vm_daily_rollup` or hourly cache
|
||||||
|
- Lifecycle reconciliation updates both canonical cache tables and prior hourly snapshot tables during the hot path.
|
||||||
|
- Report generation is still coupled to scheduled capture and aggregation jobs.
|
||||||
|
- The current UI is rendered through Templ pages and shared `web2`/`web3` CSS classes, but it does not yet match the visual system described in `design.md`.
|
||||||
|
- Current shipped styling still uses a different blue accent, tighter radii, default system typography, and inconsistent component hierarchy compared with the target design language.
|
||||||
|
|
||||||
|
## Implementation Goals
|
||||||
|
- Reduce hourly capture wall-clock time.
|
||||||
|
- Reduce daily and monthly aggregation runtime.
|
||||||
|
- Eliminate repeated historical table scans from the normal scheduled path.
|
||||||
|
- Keep user-visible HTTP APIs, reports, and auth behavior unchanged.
|
||||||
|
- Improve UI clarity and consistency so the dashboard, snapshot views, and trace views reflect the design direction in `design.md`.
|
||||||
|
- Make authentication and role requirements easier to understand from the UI without changing the auth model.
|
||||||
|
- Preserve compatibility with SQLite for development and small installs.
|
||||||
|
- Make the runtime architecture cleanly scalable for PostgreSQL production use.
|
||||||
|
|
||||||
|
## Implementation Changes
|
||||||
|
|
||||||
|
### 1. Hourly Capture Pipeline
|
||||||
|
- Keep `GetAllVMsWithProps` as the primary vCenter inventory fetch path.
|
||||||
|
- Preserve single-VM property retrieval only as a fallback path when bulk retrieval is incomplete.
|
||||||
|
- Replace row-by-row database writes in hourly capture with batched writes.
|
||||||
|
- For PostgreSQL:
|
||||||
|
- prefer multi-row insert/upsert or `COPY` into `vm_hourly_stats`
|
||||||
|
- keep conflict handling on the canonical key
|
||||||
|
- For SQLite:
|
||||||
|
- keep transactional batched insert/upsert
|
||||||
|
- do not attempt PostgreSQL-only ingestion patterns
|
||||||
|
- During capture, write data to these canonical destinations first:
|
||||||
|
- `vm_hourly_stats`
|
||||||
|
- `vm_lifecycle_cache`
|
||||||
|
- `vcenter_totals`
|
||||||
|
- `vcenter_latest_totals`
|
||||||
|
- `vcenter_aggregate_totals` for hourly totals
|
||||||
|
- Treat `inventory_hourly_<epoch>` as compatibility output, not as the source of truth for downstream jobs.
|
||||||
|
- Move deletion and event reconciliation to one post-capture reconciliation phase per vCenter.
|
||||||
|
- In that reconciliation phase, update canonical cache tables first.
|
||||||
|
- Stop updating prior hourly snapshot tables inline during the capture hot path except where compatibility mode explicitly requires it.
|
||||||
|
- Remove synchronous XLSX regeneration from hourly capture.
|
||||||
|
- Scheduled capture should finish once persistence and reconciliation are complete.
|
||||||
|
- Report generation should run after the capture path, either deferred within the job or via a follow-up stage.
|
||||||
|
|
||||||
|
### 2. Daily Aggregation
|
||||||
|
- Make `vm_hourly_stats` the only normal scheduled input for daily aggregation.
|
||||||
|
- Scheduled daily jobs must not build `UNION ALL` queries across `inventory_hourly_*`.
|
||||||
|
- Keep the Go aggregation path as the explicit default scheduled path for the current implementation and migration phases.
|
||||||
|
- Readability is the primary reason for this default: the Go path is materially easier to follow, test, and debug than the current snapshot-union SQL path.
|
||||||
|
- Performance is a secondary but still important reason: on the current implementation, Go is expected to outperform the existing SQL union path by avoiding repeated historical table scans.
|
||||||
|
- Treat the SQL path as non-default compatibility and fallback behavior.
|
||||||
|
- Do not treat this as a permanent rejection of SQL.
|
||||||
|
- Only promote SQL to default if benchmark results on canonical Postgres data show a clear, repeatable improvement over the Go path.
|
||||||
|
- Keep the current SQL union path only for:
|
||||||
|
- compatibility fallback
|
||||||
|
- manual repair
|
||||||
|
- backfill support where needed
|
||||||
|
- Daily aggregation output must continue writing:
|
||||||
|
- `inventory_daily_summary_YYYYMMDD`
|
||||||
|
- `vm_daily_rollup`
|
||||||
|
- `snapshot_registry` daily record
|
||||||
|
- refreshed `vcenter_aggregate_totals` daily entries
|
||||||
|
- Lifecycle refinement should operate on canonical lifecycle data and only use snapshot-table probing as fallback.
|
||||||
|
- Preserve existing daily semantics for:
|
||||||
|
- `SamplesPresent`
|
||||||
|
- `AvgIsPresent`
|
||||||
|
- weighted CPU/RAM/disk averages
|
||||||
|
- pool percentages
|
||||||
|
- creation/deletion time behavior
|
||||||
|
|
||||||
|
### 3. Monthly Aggregation
|
||||||
|
- Make `vm_daily_rollup` the default scheduled input for monthly aggregation.
|
||||||
|
- Scheduled monthly jobs should not scan hourly snapshot tables in the normal path.
|
||||||
|
- Keep the Go aggregation path as the explicit default scheduled path for the current implementation and migration phases.
|
||||||
|
- Readability is the primary reason for this default: the Go path is materially easier to follow, test, and debug than the current SQL path.
|
||||||
|
- Performance is a secondary but still important reason: on the current implementation, Go is expected to outperform the existing SQL path by avoiding snapshot-table unions and hourly-history scans in the normal case.
|
||||||
|
- Treat the SQL path as non-default compatibility and fallback behavior.
|
||||||
|
- Do not treat this as a permanent rejection of SQL.
|
||||||
|
- Only promote SQL to default if benchmark results on canonical Postgres data show a clear, repeatable improvement over the Go path.
|
||||||
|
- Keep hourly-based monthly aggregation only for:
|
||||||
|
- manual rebuilds
|
||||||
|
- repair/backfill workflows
|
||||||
|
- validation against old behavior
|
||||||
|
- Preserve current monthly weighting semantics based on per-day sample volumes.
|
||||||
|
- Monthly aggregation output must continue writing:
|
||||||
|
- `inventory_monthly_summary_YYYYMM`
|
||||||
|
- `snapshot_registry` monthly record
|
||||||
|
- refreshed `vcenter_aggregate_totals` monthly entries
|
||||||
|
- Keep report generation behavior unchanged from the user’s perspective, but do not keep it on the critical aggregation hot path if it can be deferred safely.
|
||||||
|
|
||||||
|
### 4. Storage and Schema
|
||||||
|
- Keep these tables during migration:
|
||||||
|
- `inventory_hourly_*`
|
||||||
|
- `inventory_daily_summary_*`
|
||||||
|
- `inventory_monthly_summary_*`
|
||||||
|
- Stop treating hourly snapshot tables as the normal scheduled aggregation source.
|
||||||
|
- Preserve `snapshot_registry`, but register logical hourly snapshots by timestamp even when downstream jobs no longer depend on hourly table scans.
|
||||||
|
- Validate or add the following indexes on `vm_hourly_stats` for PostgreSQL:
|
||||||
|
- `("SnapshotTime")`
|
||||||
|
- `("Vcenter","SnapshotTime")`
|
||||||
|
- `("Vcenter","VmId","SnapshotTime")`
|
||||||
|
- `("Vcenter","VmUuid","SnapshotTime")`
|
||||||
|
- a name lookup index aligned with current trace queries
|
||||||
|
- Keep the existing trace-compatible indexes for SQLite.
|
||||||
|
- After the canonical-path migration is stable, partition `vm_hourly_stats` by snapshot month for PostgreSQL.
|
||||||
|
- Do not require partitioning for SQLite or tests.
|
||||||
|
|
||||||
|
### 5. Compatibility Mode
|
||||||
|
- Introduce an explicit compatibility mode for legacy snapshot tables.
|
||||||
|
- When compatibility mode is enabled:
|
||||||
|
- continue writing `inventory_hourly_*`
|
||||||
|
- continue generating legacy-compatible daily/monthly summary tables
|
||||||
|
- continue registering snapshots as today
|
||||||
|
- When compatibility mode is disabled in a later phase:
|
||||||
|
- scheduled jobs may skip legacy hourly table creation
|
||||||
|
- compatibility reports and endpoints must still work from canonical data or compatibility rebuild jobs
|
||||||
|
- Default to compatibility mode enabled during the transition.
|
||||||
|
|
||||||
|
### 6. Scheduling and Job Flow
|
||||||
|
- Refactor the scheduled pipeline into explicit stages:
|
||||||
|
1. capture
|
||||||
|
2. reconcile
|
||||||
|
3. register and refresh totals caches
|
||||||
|
4. optional report generation
|
||||||
|
- Daily aggregation should run only against the completed prior-day hourly data.
|
||||||
|
- Monthly aggregation should depend on daily rollup completion, not hourly history scans.
|
||||||
|
- Keep the current cron behavior and auth/UI behavior unchanged while internal data flow changes land.
|
||||||
|
- Backfill and repair jobs should rebuild canonical caches first, then compatibility tables and reports.
|
||||||
|
|
||||||
|
### 7. UI Refresh and Design-System Alignment
|
||||||
|
- Use `design.md` as the source of truth for the UI refresh, but adapt it pragmatically to this codebase rather than attempting a pixel-perfect clone.
|
||||||
|
- Introduce semantic theme tokens using `--theme_*` naming in the shared stylesheet layer.
|
||||||
|
- Replace the current ad hoc `web2` color and radius values with tokenized equivalents for:
|
||||||
|
- primary text
|
||||||
|
- weak text
|
||||||
|
- CTA blue
|
||||||
|
- borders
|
||||||
|
- surfaces
|
||||||
|
- success states
|
||||||
|
- button spotlight text
|
||||||
|
- card and ambient shadows
|
||||||
|
- Update the shared stylesheet source and shipped compiled assets so the new tokens flow through the delivered UI.
|
||||||
|
- Keep the existing `web2` and `web3` class names if that reduces churn, but rebase them on the new token system.
|
||||||
|
- Establish a typography strategy that follows `design.md` while remaining deployable:
|
||||||
|
- prefer Haas and Haas Groot Disp only if licensed webfont delivery is available
|
||||||
|
- otherwise define a documented fallback stack with similar proportions and spacing behavior
|
||||||
|
- apply positive letter spacing to body, caption, and button treatments where appropriate
|
||||||
|
- Normalize component shape language to the design brief:
|
||||||
|
- buttons at 12px radius
|
||||||
|
- cards and sections at 16px to 24px radius
|
||||||
|
- larger containers at 24px to 32px radius where needed
|
||||||
|
- avoid the current 3px to 6px rounded treatment as the default visual language
|
||||||
|
- Replace the current flat visual treatment with the documented blue-tinted shadow system, but keep shadows controlled and readable in data-heavy views.
|
||||||
|
- Refactor shared UI structure in the Templ layer:
|
||||||
|
- `components/core/header.templ`
|
||||||
|
- `components/core/footer.templ`
|
||||||
|
- shared shell/header/card/button/table/form patterns used across `components/views/*`
|
||||||
|
- Add a reusable page-shell pattern so all primary pages share:
|
||||||
|
- a consistent hero/header treatment
|
||||||
|
- action grouping
|
||||||
|
- content width rules
|
||||||
|
- section spacing
|
||||||
|
- responsive table overflow behavior
|
||||||
|
- Improve the dashboard information architecture in `components/views/index.templ`:
|
||||||
|
- reduce the current long-form text density
|
||||||
|
- promote primary navigation and key operational tasks
|
||||||
|
- move build metadata into secondary status cards
|
||||||
|
- present auth requirements and role policy as a concise callout rather than dense paragraph copy
|
||||||
|
- Improve snapshot and vCenter list pages in `components/views/snapshots.templ`:
|
||||||
|
- stronger table hierarchy
|
||||||
|
- clearer record counts and grouping
|
||||||
|
- more intentional page headers and return navigation
|
||||||
|
- responsive behavior that preserves readability on smaller screens
|
||||||
|
- Improve the VM trace page in `components/views/vm_trace.templ`:
|
||||||
|
- upgrade search form layout and input styling
|
||||||
|
- improve chart framing and diagnostics presentation
|
||||||
|
- make lifecycle summary cards visually clearer
|
||||||
|
- preserve dense tabular detail without making the page feel purely utilitarian
|
||||||
|
- Ensure the auth-enabled experience is visible in the UI:
|
||||||
|
- clarify that UI pages remain public while APIs require Bearer tokens when auth is enabled
|
||||||
|
- surface viewer versus admin capability differences in concise language
|
||||||
|
- keep Swagger and operational links accessible from the main navigation
|
||||||
|
- Add accessibility and interaction requirements to the UI implementation:
|
||||||
|
- visible focus states
|
||||||
|
- sufficient text/background contrast
|
||||||
|
- keyboard-usable navigation and forms
|
||||||
|
- table layouts that remain readable with horizontal overflow
|
||||||
|
- mobile-safe spacing and tap targets
|
||||||
|
- Keep UI changes implementation-friendly:
|
||||||
|
- avoid introducing a large frontend framework
|
||||||
|
- continue using Templ plus shared CSS and existing JS assets
|
||||||
|
- prefer incremental component replacement over a full frontend rewrite
|
||||||
|
|
||||||
|
## Public Interfaces and Settings
|
||||||
|
- No HTTP API changes are required.
|
||||||
|
- Keep existing endpoints and report filenames stable.
|
||||||
|
- No auth-model changes are required for the UI refresh.
|
||||||
|
- If licensed fonts are not available for deployment, the implementation must ship with a documented fallback stack rather than blocking the UI work.
|
||||||
|
- Add these settings:
|
||||||
|
- `settings.capture_write_batch_size`
|
||||||
|
- default: `1000`
|
||||||
|
- controls batched DB writes for hourly capture
|
||||||
|
- `settings.snapshot_table_compat_mode`
|
||||||
|
- default: `true`
|
||||||
|
- when `true`, continue writing legacy snapshot tables during migration
|
||||||
|
- `settings.async_report_generation`
|
||||||
|
- default: `true`
|
||||||
|
- when `true`, scheduled jobs defer XLSX generation from the hot path
|
||||||
|
- Keep existing settings such as:
|
||||||
|
- `hourly_snapshot_concurrency`
|
||||||
|
- `monthly_aggregation_granularity`
|
||||||
|
- retry settings
|
||||||
|
- cleanup settings
|
||||||
|
- Scheduled monthly aggregation should ignore hourly granularity unless running a manual or backfill job.
|
||||||
|
|
||||||
|
## Execution Order
|
||||||
|
|
||||||
|
### Phase 1: Hot-Path Runtime Wins
|
||||||
|
- Add batched hourly writes.
|
||||||
|
- Decouple report generation from hourly capture.
|
||||||
|
- Ensure daily scheduled aggregation reads only from `vm_hourly_stats`.
|
||||||
|
- Ensure monthly scheduled aggregation reads only from `vm_daily_rollup`.
|
||||||
|
- Keep compatibility tables enabled.
|
||||||
|
- Define the UI token layer and shared component mapping before page-level redesign work begins.
|
||||||
|
|
||||||
|
### Phase 2: Canonical Dataflow
|
||||||
|
- Refactor reconciliation so canonical caches are updated first.
|
||||||
|
- Reduce or eliminate prior-snapshot table mutations during capture.
|
||||||
|
- Make scheduled aggregation paths canonical-only.
|
||||||
|
- Keep fallback and repair code for legacy unions/scans.
|
||||||
|
- Implement the shared page shell, navigation, button, card, table, and form refinements across the existing Templ views.
|
||||||
|
|
||||||
|
### Phase 3: Postgres-Ready Scale-Up
|
||||||
|
- Validate index coverage on canonical tables.
|
||||||
|
- Add PostgreSQL partitioning for `vm_hourly_stats`.
|
||||||
|
- Benchmark Go and SQL aggregation paths on representative production-scale data.
|
||||||
|
- Keep Go as default unless SQL demonstrates a clear, repeatable runtime win on canonical Postgres data.
|
||||||
|
- Treat the benchmark as a comparison against a canonical-table SQL implementation, not the current snapshot-union SQL path.
|
||||||
|
- If SQL wins, promote SQL behind a controlled rollout flag first, then make it default.
|
||||||
|
- Complete page-specific UI refinement for dashboard, snapshots, vCenter totals, and VM trace using the shared tokenized design system.
|
||||||
|
|
||||||
|
### Phase 4: Compatibility Reduction
|
||||||
|
- Keep legacy table output behind `snapshot_table_compat_mode`.
|
||||||
|
- Once canonical-path validation is complete, allow disabling legacy hourly table generation in scheduled runs.
|
||||||
|
- Retain explicit backfill and rebuild commands for compatibility tables and reports.
|
||||||
|
- Clean up obsolete styling rules and duplicated visual patterns once the new UI system is fully adopted.
|
||||||
|
|
||||||
|
## Test Plan
|
||||||
|
|
||||||
|
### Correctness Tests
|
||||||
|
- Add golden-result tests comparing old and new daily outputs for the same synthetic hourly dataset.
|
||||||
|
- Add golden-result tests comparing old and new monthly outputs for the same synthetic daily dataset.
|
||||||
|
- Include edge cases for:
|
||||||
|
- partial-day VM presence
|
||||||
|
- missing creation times
|
||||||
|
- deletion-time refinement
|
||||||
|
- pool changes
|
||||||
|
- CPU and RAM changes across samples
|
||||||
|
- VMs identified by `VmId`, `VmUuid`, and fallback name matching
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- Hourly capture writes `vm_hourly_stats`, lifecycle caches, and vCenter totals correctly.
|
||||||
|
- Daily aggregation reads canonical hourly data without scanning `inventory_hourly_*`.
|
||||||
|
- Monthly aggregation reads canonical daily rollup without scanning hourly history in the normal path.
|
||||||
|
- `vcenter_aggregate_totals` remains correct for hourly, daily, and monthly views.
|
||||||
|
- Trace and totals endpoints keep returning equivalent results before and after migration.
|
||||||
|
- UI page rendering remains valid for dashboard, snapshot pages, vCenter totals, and VM trace after shared component changes.
|
||||||
|
|
||||||
|
### Compatibility Tests
|
||||||
|
- When `snapshot_table_compat_mode=true`, compatibility snapshot tables still exist and are populated.
|
||||||
|
- Reports still generate correctly from migrated data.
|
||||||
|
- Backfill and repair flows can rebuild compatibility outputs from canonical sources.
|
||||||
|
- UI remains functional when auth is disabled and when auth is enabled with protected API usage documented in-page.
|
||||||
|
|
||||||
|
### Performance Tests
|
||||||
|
- Measure per-vCenter capture duration.
|
||||||
|
- Measure hourly write throughput.
|
||||||
|
- Measure daily aggregation runtime.
|
||||||
|
- Measure monthly aggregation runtime.
|
||||||
|
- Measure report generation runtime when decoupled from scheduled jobs.
|
||||||
|
- Capture baseline metrics before refactor and compare after each phase.
|
||||||
|
- Measure basic UI payload impact after the refresh so stylesheet and JS growth stay controlled.
|
||||||
|
|
||||||
|
### UI Validation
|
||||||
|
- Verify token usage in shared CSS so colors, radii, and shadows are not hard-coded inconsistently across pages.
|
||||||
|
- Verify responsive behavior for dashboard, snapshot tables, vCenter totals, and VM trace at mobile and desktop widths.
|
||||||
|
- Verify focus states, contrast, and keyboard access for links, buttons, inputs, and table navigation surfaces.
|
||||||
|
- Verify that the auth guidance on the dashboard still matches actual route protection and Bearer-token behavior.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- Scheduled hourly capture runtime is materially reduced without changing user-visible outputs.
|
||||||
|
- Scheduled daily aggregation no longer depends on `inventory_hourly_*` scans.
|
||||||
|
- Scheduled monthly aggregation no longer depends on hourly-history scans.
|
||||||
|
- Canonical caches become the source of truth for normal scheduled processing.
|
||||||
|
- Legacy compatibility behavior remains available during migration.
|
||||||
|
- Existing endpoints, reports, auth behavior, and operational commands continue to work.
|
||||||
|
- The UI reflects the design direction in `design.md` through tokenized colors, typography, spacing, radius, and shadow usage.
|
||||||
|
- The dashboard, snapshot pages, vCenter totals view, and VM trace view share a coherent visual system and clearer information hierarchy.
|
||||||
|
- The refreshed UI remains responsive, accessible, and compatible with the current Templ-based rendering model.
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
- Target direction is Postgres-ready and runtime-first.
|
||||||
|
- Existing endpoints, report filenames, and user-visible semantics must remain stable.
|
||||||
|
- SQLite remains supported for development, tests, and smaller installs.
|
||||||
|
- PostgreSQL is the intended scale-up target for larger environments.
|
||||||
|
- Compatibility snapshot tables should remain enabled by default until canonical-path validation is complete.
|
||||||
Reference in New Issue
Block a user