From 494589001ae703a72493726b94392c01f733d74d Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 21:54:12 -0700 Subject: [PATCH] admin: add monitoring page templates Six server-rendered pages (overview plus volume server, filer, S3, master and worker detail) built from shared chartCard and statCard components, following the existing Bootstrap card and table conventions. Charts are inline SVG, so no chart library is pulled in. Pages show a 'no metrics yet' notice until the first scrapes land. --- weed/admin/view/app/monitoring.templ | 347 ++++++++ weed/admin/view/app/monitoring_templ.go | 1047 +++++++++++++++++++++++ weed/admin/view/app/template_helpers.go | 9 + 3 files changed, 1403 insertions(+) create mode 100644 weed/admin/view/app/monitoring.templ create mode 100644 weed/admin/view/app/monitoring_templ.go diff --git a/weed/admin/view/app/monitoring.templ b/weed/admin/view/app/monitoring.templ new file mode 100644 index 000000000..7275b4c56 --- /dev/null +++ b/weed/admin/view/app/monitoring.templ @@ -0,0 +1,347 @@ +package app + +import ( + "fmt" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" +) + +// chartCard renders one titled chart with a legend. +templ chartCard(title string, icon string, width string, series []dash.ChartSeries, opts dash.ChartOptions) { +
+
+
+
+ { title } +
+
+
+ @templ.Raw(dash.RenderChart(series, opts)) + @templ.Raw(dash.RenderLegend(series)) +
+
+
+} + +// statCard shows a single headline number with a colored left border. +templ statCard(label string, value string, icon string, accent string) { +
+
+
+
+
+
+ { label } +
+
{ value }
+
+
+ +
+
+
+
+
+} + +templ monitoringHeader(title string, icon string, lastUpdated string) { +
+

+ { title } +

+ + Updated { lastUpdated } + +
+} + +templ noMetricsNotice() { + +} + +templ Monitoring(data dash.MonitoringData) { + @monitoringHeader("Monitoring Overview", "fa-chart-line", data.LastUpdated.Format("15:04:05")) + + if len(data.Overview.UnderReplicatedVolumes) == 0 && len(data.Overview.VolumeReadRate) == 0 { + @noMetricsNotice() + } + + if dash.LatestValue(data.Overview.UnderReplicatedVolumes) > 0 { + + } + +
+ @statCard("Under-replicated", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.UnderReplicatedVolumes)), "fa-clone", "danger") + @statCard("Writable Volumes", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.WritableVolumes)), "fa-pen", "success") + @statCard("Disk Usage", dash.FormatChartValue(dash.LatestValue(data.Overview.DiskUsagePct), dash.UnitPercent), "fa-hdd", "warning") + @statCard("Queue Depth", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.QueueDepth)), "fa-tasks", "info") +
+ +
Health
+
+ @chartCard("Under-replicated Volumes", "fa-clone", "col-xl-6", []dash.ChartSeries{ + {Name: "Under-replicated", Color: dash.ChartDanger, Data: data.Overview.UnderReplicatedVolumes, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Volume Capacity Pressure", "fa-pen", "col-xl-6", []dash.ChartSeries{ + {Name: "Writable", Color: dash.ChartSuccess, Data: data.Overview.WritableVolumes}, + {Name: "Crowded", Color: dash.ChartWarning, Data: data.Overview.CrowdedVolumes}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Cluster Disk Usage", "fa-hdd", "col-xl-6", []dash.ChartSeries{ + {Name: "Disk used", Color: dash.ChartWarning, Data: data.Overview.DiskUsagePct, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitPercent, Threshold: diskThreshold()}) + @chartCard("Disk & Quarantine Errors", "fa-triangle-exclamation", "col-xl-6", []dash.ChartSeries{ + {Name: "Disk errors", Color: dash.ChartDanger, Data: data.Overview.DiskErrors}, + {Name: "Quarantined", Color: dash.ChartWarning, Data: data.Overview.Quarantined}, + }, dash.ChartOptions{Unit: dash.UnitCount}) +
+ +
Throughput
+
+ @chartCard("Volume Server Request Rate", "fa-server", "col-xl-6", []dash.ChartSeries{ + {Name: "Read", Color: dash.ChartPrimary, Data: data.Overview.VolumeReadRate}, + {Name: "Write", Color: dash.ChartSuccess, Data: data.Overview.VolumeWriteRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Filer & S3 Request Rate", "fa-folder-open", "col-xl-6", []dash.ChartSeries{ + {Name: "Filer", Color: dash.ChartInfo, Data: data.Overview.FilerRequestRate}, + {Name: "S3", Color: dash.ChartPrimary, Data: data.Overview.S3RequestRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}) +
+ +
Latency
+
+ @chartCard("Volume Server Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p50", Color: dash.ChartSuccess, Data: data.Overview.VolumeP50}, + {Name: "p95", Color: dash.ChartWarning, Data: data.Overview.VolumeP95}, + {Name: "p99", Color: dash.ChartDanger, Data: data.Overview.VolumeP99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}) + @chartCard("Filer Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p50", Color: dash.ChartSuccess, Data: data.Overview.FilerP50}, + {Name: "p95", Color: dash.ChartWarning, Data: data.Overview.FilerP95}, + {Name: "p99", Color: dash.ChartDanger, Data: data.Overview.FilerP99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}) +
+ +
Errors
+
+ @chartCard("Request Error Rate by Component", "fa-bug", "col-xl-6", []dash.ChartSeries{ + {Name: "Volume", Color: dash.ChartDanger, Data: data.Overview.VolumeErrorRate}, + {Name: "Filer", Color: dash.ChartWarning, Data: data.Overview.FilerErrorRate}, + {Name: "S3", Color: dash.ChartInfo, Data: data.Overview.S3ErrorRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Maintenance Queue & Worker Slots", "fa-tasks", "col-xl-6", []dash.ChartSeries{ + {Name: "Queued tasks", Color: dash.ChartInfo, Data: data.Overview.QueueDepth}, + {Name: "Slots used", Color: dash.ChartPrimary, Data: data.Overview.SlotsUsed}, + {Name: "Slots max", Color: dash.ChartWarning, Data: data.Overview.SlotsMax}, + }, dash.ChartOptions{Unit: dash.UnitCount}) +
+} + +templ MonitoringVolumeServers(data dash.MonitoringData) { + @monitoringHeader("Volume Servers", "fa-server", data.LastUpdated.Format("15:04:05")) + + if len(data.VolumeServers) == 0 { + @noMetricsNotice() + } + +
+
+
+ Server Health +
+
+
+
+ + + + + + + + + + + + for _, vs := range data.VolumeServers { + + + + + + + + } + +
ServerRequest Ratep99 LatencyDisk UsageError Rate
{ vs.Address }{ dash.FormatChartValue(dash.LatestValue(vs.RequestRate), dash.UnitCount) }/s{ dash.FormatChartValue(dash.LatestValue(vs.P99), dash.UnitSeconds) }{ dash.FormatChartValue(dash.LatestValue(vs.DiskUsagePct), dash.UnitPercent) } + if dash.LatestValue(vs.ErrorRate) > 0 { + + { dash.FormatChartValue(dash.LatestValue(vs.ErrorRate), dash.UnitCount) }/s + + } else { + 0 + } +
+
+
+
+ + for _, vs := range data.VolumeServers { +
{ vs.Address }
+
+ @chartCard("Request Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{ + {Name: "Requests", Color: dash.ChartPrimary, Data: vs.RequestRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("p99 Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p99", Color: dash.ChartDanger, Data: vs.P99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}) + @chartCard("Disk Usage", "fa-hdd", "col-xl-6", []dash.ChartSeries{ + {Name: "Disk used", Color: dash.ChartWarning, Data: vs.DiskUsagePct, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitPercent, Threshold: diskThreshold()}) + @chartCard("Error Rate", "fa-bug", "col-xl-6", []dash.ChartSeries{ + {Name: "Errors", Color: dash.ChartDanger, Data: vs.ErrorRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}) +
+ } +} + +templ MonitoringFilers(data dash.MonitoringData) { + @monitoringHeader("Filers", "fa-folder-open", data.LastUpdated.Format("15:04:05")) + + if len(data.Filers) == 0 { + @noMetricsNotice() + } + + for _, f := range data.Filers { +
{ f.Address }
+
+ @chartCard("Request Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{ + {Name: "Requests", Color: dash.ChartInfo, Data: f.RequestRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "Filer p99", Color: dash.ChartDanger, Data: f.P99}, + {Name: "Store p99", Color: dash.ChartWarning, Data: f.StoreP99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}) + @chartCard("In-flight Requests", "fa-spinner", "col-xl-6", []dash.ChartSeries{ + {Name: "In flight", Color: dash.ChartPrimary, Data: f.InFlight, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Replication Lag", "fa-clock-rotate-left", "col-xl-6", []dash.ChartSeries{ + {Name: "Lag", Color: dash.ChartWarning, Data: f.SyncLag}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}) +
+ } +} + +templ MonitoringS3(data dash.MonitoringData) { + @monitoringHeader("S3", "fa-cloud", data.LastUpdated.Format("15:04:05")) + + if len(data.S3) == 0 { + @noMetricsNotice() + } + + for _, n := range data.S3 { +
{ n.Address }
+
+ @chartCard("Operation Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{ + {Name: "Requests", Color: dash.ChartPrimary, Data: n.RequestRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Error Rate", "fa-bug", "col-xl-6", []dash.ChartSeries{ + {Name: "4xx", Color: dash.ChartWarning, Data: n.Errors4xx}, + {Name: "5xx", Color: dash.ChartDanger, Data: n.Errors5xx}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("p99 Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p99", Color: dash.ChartDanger, Data: n.P99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}) +
+ } +} + +templ MonitoringMasters(data dash.MonitoringData) { + @monitoringHeader("Masters", "fa-crown", data.LastUpdated.Format("15:04:05")) + + if len(data.Masters) == 0 { + @noMetricsNotice() + } + + for _, m := range data.Masters { +
+ { m.Address } + if m.IsLeader { + + Leader + + } +
+
+ @chartCard("Heartbeat Rate", "fa-heartbeat", "col-xl-6", []dash.ChartSeries{ + {Name: "Heartbeats", Color: dash.ChartSuccess, Data: m.HeartbeatRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Volume Creation Rate", "fa-plus", "col-xl-6", []dash.ChartSeries{ + {Name: "Created", Color: dash.ChartPrimary, Data: m.VolumeCreation}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Leader Changes", "fa-random", "col-xl-6", []dash.ChartSeries{ + {Name: "Changes", Color: dash.ChartDanger, Data: m.LeaderChanges}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Placement Mismatches", "fa-triangle-exclamation", "col-xl-6", []dash.ChartSeries{ + {Name: "Mismatched", Color: dash.ChartWarning, Data: m.PlacementMiss}, + }, dash.ChartOptions{Unit: dash.UnitCount}) +
+ } +} + +templ MonitoringWorkers(data dash.MonitoringData) { + @monitoringHeader("Workers", "fa-users-cog", data.LastUpdated.Format("15:04:05")) + +
+ @statCard("Connected Workers", fmt.Sprintf("%d", data.Workers.Connected), "fa-users", "primary") + @statCard("Queue Depth", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.QueueDepth)), "fa-tasks", "info") + @statCard("Slots Used", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.SlotsUsed)), "fa-microchip", "success") + @statCard("Slots Max", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.SlotsMax)), "fa-layer-group", "warning") +
+ +
+ @chartCard("Task Queue Depth", "fa-tasks", "col-xl-6", []dash.ChartSeries{ + {Name: "Queued", Color: dash.ChartInfo, Data: data.Workers.QueueDepth, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Worker Slots", "fa-microchip", "col-xl-6", []dash.ChartSeries{ + {Name: "Used", Color: dash.ChartPrimary, Data: data.Workers.SlotsUsed}, + {Name: "Max", Color: dash.ChartWarning, Data: data.Workers.SlotsMax}, + }, dash.ChartOptions{Unit: dash.UnitCount}) + @chartCard("Task Completion Rate", "fa-check", "col-xl-6", []dash.ChartSeries{ + {Name: "Completed", Color: dash.ChartSuccess, Data: data.Workers.TaskRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}) +
+ +
+
+
+ Connected Workers +
+
+
+ if len(data.Workers.Workers) > 0 { +
+ + + + for _, w := range data.Workers.Workers { + + } + +
Worker ID
{ w.ID }
+
+ } else { +
+ +

No workers are currently connected.

+
+ } +
+
+} diff --git a/weed/admin/view/app/monitoring_templ.go b/weed/admin/view/app/monitoring_templ.go new file mode 100644 index 000000000..1b7aef12c --- /dev/null +++ b/weed/admin/view/app/monitoring_templ.go @@ -0,0 +1,1047 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package app + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" +) + +// chartCard renders one titled chart with a legend. +func chartCard(title string, icon string, width string, series []dash.ChartSeries, opts dash.ChartOptions) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var2 = []any{width, "mb-4"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 = []any{"fas", icon, "me-2"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 14, Col: 49} + } + _, 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, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(dash.RenderChart(series, opts)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(dash.RenderLegend(series)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// statCard shows a single headline number with a colored left border. +func statCard(label string, value string, icon string, accent string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 = []any{"card", "border-left-" + accent, "shadow", "h-100", "py-2"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 = []any{"text-xs", "font-weight-bold", "text-" + accent, "text-uppercase", "mb-1"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 33, Col: 14} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 35, Col: 65} + } + _, 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, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 = []any{"fas", icon, "fa-2x", "text-gray-300"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func monitoringHeader(title string, icon string, lastUpdated string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 = []any{"fas", icon, "me-2"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 49, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

Updated ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(lastUpdated) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 52, Col: 57} + } + _, 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, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func noMetricsNotice() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var21 := templ.GetChildren(ctx) + if templ_7745c5c3_Var21 == nil { + templ_7745c5c3_Var21 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
No metrics collected yet. The admin server scrapes each node's /metrics endpoint every 15 seconds; charts fill in as samples accumulate.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func Monitoring(data dash.MonitoringData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var22 := templ.GetChildren(ctx) + if templ_7745c5c3_Var22 == nil { + templ_7745c5c3_Var22 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = monitoringHeader("Monitoring Overview", "fa-chart-line", data.LastUpdated.Format("15:04:05")).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Overview.UnderReplicatedVolumes) == 0 && len(data.Overview.VolumeReadRate) == 0 { + templ_7745c5c3_Err = noMetricsNotice().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if dash.LatestValue(data.Overview.UnderReplicatedVolumes) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.UnderReplicatedVolumes))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 75, Col: 88} + } + _, 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, 23, " under-replicated volume(s). Data is at risk until replication catches up.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Under-replicated", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.UnderReplicatedVolumes)), "fa-clone", "danger").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Writable Volumes", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.WritableVolumes)), "fa-pen", "success").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Disk Usage", dash.FormatChartValue(dash.LatestValue(data.Overview.DiskUsagePct), dash.UnitPercent), "fa-hdd", "warning").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Queue Depth", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.QueueDepth)), "fa-tasks", "info").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Health
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Under-replicated Volumes", "fa-clone", "col-xl-6", []dash.ChartSeries{ + {Name: "Under-replicated", Color: dash.ChartDanger, Data: data.Overview.UnderReplicatedVolumes, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Volume Capacity Pressure", "fa-pen", "col-xl-6", []dash.ChartSeries{ + {Name: "Writable", Color: dash.ChartSuccess, Data: data.Overview.WritableVolumes}, + {Name: "Crowded", Color: dash.ChartWarning, Data: data.Overview.CrowdedVolumes}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Cluster Disk Usage", "fa-hdd", "col-xl-6", []dash.ChartSeries{ + {Name: "Disk used", Color: dash.ChartWarning, Data: data.Overview.DiskUsagePct, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitPercent, Threshold: diskThreshold()}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Disk & Quarantine Errors", "fa-triangle-exclamation", "col-xl-6", []dash.ChartSeries{ + {Name: "Disk errors", Color: dash.ChartDanger, Data: data.Overview.DiskErrors}, + {Name: "Quarantined", Color: dash.ChartWarning, Data: data.Overview.Quarantined}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
Throughput
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Volume Server Request Rate", "fa-server", "col-xl-6", []dash.ChartSeries{ + {Name: "Read", Color: dash.ChartPrimary, Data: data.Overview.VolumeReadRate}, + {Name: "Write", Color: dash.ChartSuccess, Data: data.Overview.VolumeWriteRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Filer & S3 Request Rate", "fa-folder-open", "col-xl-6", []dash.ChartSeries{ + {Name: "Filer", Color: dash.ChartInfo, Data: data.Overview.FilerRequestRate}, + {Name: "S3", Color: dash.ChartPrimary, Data: data.Overview.S3RequestRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Latency
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Volume Server Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p50", Color: dash.ChartSuccess, Data: data.Overview.VolumeP50}, + {Name: "p95", Color: dash.ChartWarning, Data: data.Overview.VolumeP95}, + {Name: "p99", Color: dash.ChartDanger, Data: data.Overview.VolumeP99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Filer Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p50", Color: dash.ChartSuccess, Data: data.Overview.FilerP50}, + {Name: "p95", Color: dash.ChartWarning, Data: data.Overview.FilerP95}, + {Name: "p99", Color: dash.ChartDanger, Data: data.Overview.FilerP99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
Errors
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Request Error Rate by Component", "fa-bug", "col-xl-6", []dash.ChartSeries{ + {Name: "Volume", Color: dash.ChartDanger, Data: data.Overview.VolumeErrorRate}, + {Name: "Filer", Color: dash.ChartWarning, Data: data.Overview.FilerErrorRate}, + {Name: "S3", Color: dash.ChartInfo, Data: data.Overview.S3ErrorRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Maintenance Queue & Worker Slots", "fa-tasks", "col-xl-6", []dash.ChartSeries{ + {Name: "Queued tasks", Color: dash.ChartInfo, Data: data.Overview.QueueDepth}, + {Name: "Slots used", Color: dash.ChartPrimary, Data: data.Overview.SlotsUsed}, + {Name: "Slots max", Color: dash.ChartWarning, Data: data.Overview.SlotsMax}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func MonitoringVolumeServers(data dash.MonitoringData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var24 := templ.GetChildren(ctx) + if templ_7745c5c3_Var24 == nil { + templ_7745c5c3_Var24 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = monitoringHeader("Volume Servers", "fa-server", data.LastUpdated.Format("15:04:05")).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.VolumeServers) == 0 { + templ_7745c5c3_Err = noMetricsNotice().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
Server Health
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, vs := range data.VolumeServers { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
ServerRequest Ratep99 LatencyDisk UsageError Rate
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(vs.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 174, 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, 32, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(dash.FormatChartValue(dash.LatestValue(vs.RequestRate), dash.UnitCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 175, Col: 85} + } + _, 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, "/s") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(dash.FormatChartValue(dash.LatestValue(vs.P99), dash.UnitSeconds)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 176, Col: 79} + } + _, 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, 34, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(dash.FormatChartValue(dash.LatestValue(vs.DiskUsagePct), dash.UnitPercent)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 177, Col: 88} + } + _, 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, 35, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if dash.LatestValue(vs.ErrorRate) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(dash.FormatChartValue(dash.LatestValue(vs.ErrorRate), dash.UnitCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 181, Col: 82} + } + _, 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, 37, "/s") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "0") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, vs := range data.VolumeServers { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(vs.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 196, Col: 63} + } + _, 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, 42, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Request Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{ + {Name: "Requests", Color: dash.ChartPrimary, Data: vs.RequestRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("p99 Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p99", Color: dash.ChartDanger, Data: vs.P99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Disk Usage", "fa-hdd", "col-xl-6", []dash.ChartSeries{ + {Name: "Disk used", Color: dash.ChartWarning, Data: vs.DiskUsagePct, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitPercent, Threshold: diskThreshold()}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Error Rate", "fa-bug", "col-xl-6", []dash.ChartSeries{ + {Name: "Errors", Color: dash.ChartDanger, Data: vs.ErrorRate}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func MonitoringFilers(data dash.MonitoringData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var31 := templ.GetChildren(ctx) + if templ_7745c5c3_Var31 == nil { + templ_7745c5c3_Var31 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = monitoringHeader("Filers", "fa-folder-open", data.LastUpdated.Format("15:04:05")).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Filers) == 0 { + templ_7745c5c3_Err = noMetricsNotice().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, f := range data.Filers { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(f.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 222, Col: 62} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Request Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{ + {Name: "Requests", Color: dash.ChartInfo, Data: f.RequestRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "Filer p99", Color: dash.ChartDanger, Data: f.P99}, + {Name: "Store p99", Color: dash.ChartWarning, Data: f.StoreP99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("In-flight Requests", "fa-spinner", "col-xl-6", []dash.ChartSeries{ + {Name: "In flight", Color: dash.ChartPrimary, Data: f.InFlight, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Replication Lag", "fa-clock-rotate-left", "col-xl-6", []dash.ChartSeries{ + {Name: "Lag", Color: dash.ChartWarning, Data: f.SyncLag}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func MonitoringS3(data dash.MonitoringData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var33 := templ.GetChildren(ctx) + if templ_7745c5c3_Var33 == nil { + templ_7745c5c3_Var33 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = monitoringHeader("S3", "fa-cloud", data.LastUpdated.Format("15:04:05")).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.S3) == 0 { + templ_7745c5c3_Err = noMetricsNotice().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, n := range data.S3 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(n.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 249, Col: 62} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Operation Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{ + {Name: "Requests", Color: dash.ChartPrimary, Data: n.RequestRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Error Rate", "fa-bug", "col-xl-6", []dash.ChartSeries{ + {Name: "4xx", Color: dash.ChartWarning, Data: n.Errors4xx}, + {Name: "5xx", Color: dash.ChartDanger, Data: n.Errors5xx}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("p99 Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{ + {Name: "p99", Color: dash.ChartDanger, Data: n.P99}, + }, dash.ChartOptions{Unit: dash.UnitSeconds}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func MonitoringMasters(data dash.MonitoringData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var35 := templ.GetChildren(ctx) + if templ_7745c5c3_Var35 == nil { + templ_7745c5c3_Var35 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = monitoringHeader("Masters", "fa-crown", data.LastUpdated.Format("15:04:05")).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Masters) == 0 { + templ_7745c5c3_Err = noMetricsNotice().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, m := range data.Masters { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(m.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 274, Col: 14} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if m.IsLeader { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "Leader") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Heartbeat Rate", "fa-heartbeat", "col-xl-6", []dash.ChartSeries{ + {Name: "Heartbeats", Color: dash.ChartSuccess, Data: m.HeartbeatRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Volume Creation Rate", "fa-plus", "col-xl-6", []dash.ChartSeries{ + {Name: "Created", Color: dash.ChartPrimary, Data: m.VolumeCreation}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Leader Changes", "fa-random", "col-xl-6", []dash.ChartSeries{ + {Name: "Changes", Color: dash.ChartDanger, Data: m.LeaderChanges}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Placement Mismatches", "fa-triangle-exclamation", "col-xl-6", []dash.ChartSeries{ + {Name: "Mismatched", Color: dash.ChartWarning, Data: m.PlacementMiss}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func MonitoringWorkers(data dash.MonitoringData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var37 := templ.GetChildren(ctx) + if templ_7745c5c3_Var37 == nil { + templ_7745c5c3_Var37 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = monitoringHeader("Workers", "fa-users-cog", data.LastUpdated.Format("15:04:05")).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Connected Workers", fmt.Sprintf("%d", data.Workers.Connected), "fa-users", "primary").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Queue Depth", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.QueueDepth)), "fa-tasks", "info").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Slots Used", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.SlotsUsed)), "fa-microchip", "success").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = statCard("Slots Max", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.SlotsMax)), "fa-layer-group", "warning").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Task Queue Depth", "fa-tasks", "col-xl-6", []dash.ChartSeries{ + {Name: "Queued", Color: dash.ChartInfo, Data: data.Workers.QueueDepth, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Worker Slots", "fa-microchip", "col-xl-6", []dash.ChartSeries{ + {Name: "Used", Color: dash.ChartPrimary, Data: data.Workers.SlotsUsed}, + {Name: "Max", Color: dash.ChartWarning, Data: data.Workers.SlotsMax}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chartCard("Task Completion Rate", "fa-check", "col-xl-6", []dash.ChartSeries{ + {Name: "Completed", Color: dash.ChartSuccess, Data: data.Workers.TaskRate, Area: true}, + }, dash.ChartOptions{Unit: dash.UnitCount}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "
Connected Workers
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Workers.Workers) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, w := range data.Workers.Workers { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
Worker ID
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(w.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/monitoring.templ`, Line: 334, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "

No workers are currently connected.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/weed/admin/view/app/template_helpers.go b/weed/admin/view/app/template_helpers.go index fff28de09..f052bf61a 100644 --- a/weed/admin/view/app/template_helpers.go +++ b/weed/admin/view/app/template_helpers.go @@ -6,6 +6,15 @@ import ( "strings" ) +// diskUsageWarnPct is where disk-usage charts draw their reference line. +const diskUsageWarnPct = 85.0 + +// diskThreshold returns the disk-usage reference line for chart options. +func diskThreshold() *float64 { + v := diskUsageWarnPct + return &v +} + // formatBytes converts bytes to human readable format func formatBytes(bytes int64) string { if bytes == 0 {