mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-13 18:10:49 +02:00
* fix(admin): list all masters and dedupe EC file counts in dashboard Dashboard -> Master Nodes only ever showed the currently connected master because getMasterNodesStatus hard-coded a single entry. Replace it with a RaftListClusterServers call that returns every master in the raft group and tags the real leader, falling back to the current master only if the raft call fails. Buckets -> Object Store Buckets could render 0 objects for a bucket backed by an EC volume. Every shard holder reports the same whole-volume file_count (read from the replicated .ecx), so the first-seen value wins; if that first node had not yet finished loading .ecx it reported 0 and pinned the aggregate at 0. Take the max across reporting nodes instead. The dashboard header total_files also dropped after volumes were converted to erasure coding because getTopologyViaGRPC never folded EC file_count into topology.TotalFiles. Aggregate it with the same max/sum dedupe. * fix(admin): address PR review comments - bound RaftListClusterServers with a 3s timeout so the dashboard endpoint cannot hang on a stalled master - pre-validate raft addresses with net.SplitHostPort before calling pb.GrpcAddressToServerAddress, which otherwise glog.Fatalf's on a malformed entry and would crash the admin process - when raft is unreachable, mark the fallback master as not-leader rather than claiming leadership the code cannot verify - warn when summed EC delete_count exceeds file_count while folding into topology.TotalFiles, matching collectCollectionStats * fix(admin): distinguish empty raft response from RPC failure When RaftListClusterServers returns successfully with no servers, raft is not initialized (standalone/non-raft cluster), so the single fallback master is the leader. Only treat the fallback as a non-leader when the RPC actually failed. * fix(admin): remove misleading Objects column from S3 buckets page The bucket "Objects" column displayed needle counts from volume collection stats, not actual S3 object counts. This is confusing because a single S3 object can span multiple needles (multipart uploads, versions) and the count is inaccurate for EC volumes. Remove the ObjectCount field from S3Bucket, the Objects table column, the sort-by-objects handler, the detail-view row, and both CSV export references. * fix(admin): correct cell indexes in fallback bucket CSV export After the Objects column was removed, the fallback CSV exporter in admin.js still used stale cell indexes: cells[1] mapped to Owner (not Created), cells[2] to Created (not Size), cells[3] to Logical Size (not Quota). Align all indexes with the current table column order and include Owner, Logical Size, and Physical Size.
97 lines
11 KiB
Go
97 lines
11 KiB
Go
// Code generated by templ - DO NOT EDIT.
|
|
|
|
// templ: version: v0.3.977
|
|
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"
|
|
|
|
func PluginLane(page string, lane 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_Var1 := templ.GetChildren(ctx)
|
|
if templ_7745c5c3_Var1 == nil {
|
|
templ_7745c5c3_Var1 = templ.NopComponent
|
|
}
|
|
ctx = templ.ClearChildren(ctx)
|
|
currentPage := page
|
|
if currentPage == "" {
|
|
currentPage = "lane_workers"
|
|
}
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container-fluid\" id=\"plugin-lane-page\" data-plugin-page=\"")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
var templ_7745c5c3_Var2 string
|
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(currentPage)
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_lane.templ`, Line: 10, Col: 85}
|
|
}
|
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" data-plugin-lane=\"")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
var templ_7745c5c3_Var3 string
|
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(lane)
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_lane.templ`, Line: 10, Col: 111}
|
|
}
|
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"d-flex justify-content-between align-items-center flex-wrap gap-2\"><div><h2 class=\"mb-0\"><i class=\"fas fa-layer-group me-2\"></i>")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
var templ_7745c5c3_Var4 string
|
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(lane)
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_lane.templ`, Line: 15, Col: 86}
|
|
}
|
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " Lane Workers</h2><p class=\"text-muted mb-0\">Workers and scheduler status for the ")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
var templ_7745c5c3_Var5 string
|
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(lane)
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_lane.templ`, Line: 16, Col: 94}
|
|
}
|
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " scheduling lane.</p></div><div class=\"btn-group\"><a href=\"/plugin\" class=\"btn btn-outline-secondary\"><i class=\"fas fa-arrow-left me-1\"></i>All Workers</a> <button type=\"button\" class=\"btn btn-outline-secondary\" id=\"plugin-lane-refresh-btn\"><i class=\"fas fa-sync-alt me-1\"></i>Refresh</button></div></div></div></div><div class=\"row mb-3\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"card-title mb-0\"><i class=\"fas fa-server me-2\"></i>Lane Scheduler Status</h5></div><div class=\"card-body\" id=\"plugin-lane-scheduler-status\"><div class=\"text-center text-muted py-3\"><i class=\"fas fa-spinner fa-spin me-1\"></i>Loading scheduler status...</div></div></div></div></div><div class=\"row mb-3\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"card-title mb-0\"><i class=\"fas fa-tasks me-2\"></i>Job Types</h5></div><div class=\"card-body\" id=\"plugin-lane-job-types\"><div class=\"text-center text-muted py-3\"><i class=\"fas fa-spinner fa-spin me-1\"></i>Loading job types...</div></div></div></div></div><div class=\"row\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"card-title mb-0\"><i class=\"fas fa-plug me-2\"></i>Connected Workers</h5></div><div class=\"card-body\" id=\"plugin-lane-workers\"><div class=\"text-center text-muted py-3\"><i class=\"fas fa-spinner fa-spin me-1\"></i>Loading workers...</div></div></div></div></div></div><script>\n (function() {\n const page = document.getElementById('plugin-lane-page');\n if (!page) return;\n const lane = page.dataset.pluginLane || '';\n if (!lane) return;\n\n function fetchAndRender(url, containerId, renderFn) {\n fetch(url)\n .then(r => r.json())\n .then(data => {\n const el = document.getElementById(containerId);\n if (el) el.innerHTML = renderFn(data);\n })\n .catch(err => {\n const el = document.getElementById(containerId);\n if (el) el.innerHTML = '<div class=\"alert alert-danger\">Failed to load: ' + err.message + '</div>';\n });\n }\n\n function renderSchedulerStatus(data) {\n if (!data || !data.scheduler) return '<div class=\"text-muted\">No scheduler data</div>';\n const s = data.scheduler;\n let html = '<div class=\"row\">';\n html += '<div class=\"col-md-3\"><strong>Phase:</strong> ' + (s.current_phase || 'idle') + '</div>';\n html += '<div class=\"col-md-3\"><strong>Idle Sleep:</strong> ' + (s.idle_sleep_seconds || 0) + 's</div>';\n html += '<div class=\"col-md-3\"><strong>Current Job Type:</strong> ' + (s.current_job_type || '-') + '</div>';\n html += '<div class=\"col-md-3\"><strong>Next Detection:</strong> ' + (s.next_detection_at ? new Date(s.next_detection_at).toLocaleTimeString() : '-') + '</div>';\n html += '</div>';\n if (s.job_types && s.job_types.length > 0) {\n html += '<hr><table class=\"table table-sm table-hover mb-0\"><thead><tr><th>Job Type</th><th>Enabled</th><th>Interval</th><th>In Flight</th><th>Next Detection</th></tr></thead><tbody>';\n s.job_types.forEach(jt => {\n html += '<tr>';\n html += '<td>' + jt.job_type + '</td>';\n html += '<td>' + (jt.enabled ? '<span class=\"badge bg-success\">Yes</span>' : '<span class=\"badge bg-secondary\">No</span>') + '</td>';\n html += '<td>' + (jt.detection_interval_seconds || '-') + 's</td>';\n html += '<td>' + (jt.detection_in_flight ? '<span class=\"badge bg-warning\">Yes</span>' : 'No') + '</td>';\n html += '<td>' + (jt.next_detection_at ? new Date(jt.next_detection_at).toLocaleTimeString() : '-') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n }\n return html;\n }\n\n function renderJobTypes(data) {\n if (!data || data.length === 0) return '<div class=\"text-muted\">No job types in this lane</div>';\n const filtered = data.filter(s => s.lane === lane);\n if (filtered.length === 0) return '<div class=\"text-muted\">No job types in this lane</div>';\n let html = '<table class=\"table table-sm table-hover mb-0\"><thead><tr><th>Job Type</th><th>Enabled</th><th>Concurrency</th><th>Detection Interval</th><th>Status</th></tr></thead><tbody>';\n filtered.forEach(s => {\n html += '<tr>';\n html += '<td><a href=\"/plugin/configuration?job=' + s.job_type + '\">' + s.job_type + '</a></td>';\n html += '<td>' + (s.enabled ? '<span class=\"badge bg-success\">Yes</span>' : '<span class=\"badge bg-secondary\">No</span>') + '</td>';\n html += '<td>' + (s.global_execution_concurrency || 1) + '</td>';\n html += '<td>' + (s.detection_interval_seconds || '-') + 's</td>';\n html += '<td>' + (s.last_run_status || '-') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n return html;\n }\n\n function renderWorkers(data) {\n if (!data || data.length === 0) return '<div class=\"text-muted\">No workers connected for this lane</div>';\n let html = '<table class=\"table table-sm table-hover mb-0\"><thead><tr><th>Worker ID</th><th>Address</th><th>Job Types</th><th>Connected</th><th>Last Seen</th></tr></thead><tbody>';\n data.forEach(w => {\n const jobTypes = Object.keys(w.Capabilities || {}).join(', ');\n html += '<tr>';\n html += '<td>' + (w.WorkerID || '-') + '</td>';\n html += '<td>' + (w.Address || '-') + '</td>';\n html += '<td>' + jobTypes + '</td>';\n html += '<td>' + (w.ConnectedAt ? new Date(w.ConnectedAt).toLocaleString() : '-') + '</td>';\n html += '<td>' + (w.LastSeenAt ? new Date(w.LastSeenAt).toLocaleString() : '-') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n return html;\n }\n\n function refresh() {\n fetchAndRender('/api/plugin/scheduler-status?lane=' + lane, 'plugin-lane-scheduler-status', renderSchedulerStatus);\n fetchAndRender('/api/plugin/scheduler-states?lane=' + lane, 'plugin-lane-job-types', renderJobTypes);\n fetchAndRender('/api/plugin/workers?lane=' + lane, 'plugin-lane-workers', renderWorkers);\n }\n\n refresh();\n const btn = document.getElementById('plugin-lane-refresh-btn');\n if (btn) btn.addEventListener('click', refresh);\n })();\n </script>")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
var _ = templruntime.GeneratedTemplate
|