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) {
+
+
+
+
+ @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() {
+
+
+ No metrics collected yet. The admin server scrapes each node's /metrics
+ endpoint every 15 seconds; charts fill in as samples accumulate.
+
+}
+
+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 {
+
+
+ { fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.UnderReplicatedVolumes)) }
+ under-replicated volume(s). Data is at risk until replication catches up.
+
+ }
+
+
+ @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 |
+ Request Rate |
+ p99 Latency |
+ Disk Usage |
+ Error Rate |
+
+
+
+ for _, vs := range data.VolumeServers {
+
+ | { 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})
+
+
+
+
+
+ if len(data.Workers.Workers) > 0 {
+
+
+ | Worker ID |
+
+ for _, w := range data.Workers.Workers {
+ | { 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
+ }
+ 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 | Request Rate | p99 Latency | Disk Usage | Error Rate |
")
+ 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
+ }
+ 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
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
")
+ 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, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(data.Workers.Workers) > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
| Worker ID |
")
+ 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
+ }
+ 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
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
")
+ 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 {