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 {