From 3fe2d76bde7f8a944f1d823378d4557936e0be01 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 8 Mar 2026 22:57:10 -0700 Subject: [PATCH] admin UI: add volume balance execution plan and batch badge Add renderBalanceExecutionPlan() for rich rendering of volume balance jobs in the job detail modal. Single-move jobs show source/target/volume info; batch jobs show a moves table with all volume moves. Add batch badge (e.g., "5 moves") next to job type in the execution jobs table when the job has batch=true label. --- weed/admin/view/app/plugin.templ | 49 ++++++++++++++++++++++++++++- weed/admin/view/app/plugin_templ.go | 4 +-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/weed/admin/view/app/plugin.templ b/weed/admin/view/app/plugin.templ index 2b9e8fe27..a75297416 100644 --- a/weed/admin/view/app/plugin.templ +++ b/weed/admin/view/app/plugin.templ @@ -1026,6 +1026,9 @@ templ Plugin(page string) { } var jobType = String(plan.job_type || '').trim().toLowerCase(); + if (jobType === 'volume_balance') { + return renderBalanceExecutionPlan(plan); + } if (jobType !== 'erasure_coding') { var fallbackText = toPrettyJson(plan); if (!fallbackText) { @@ -1107,6 +1110,44 @@ templ Plugin(page string) { return html; } + function renderBalanceExecutionPlan(plan) { + var html = '
Execution Plan
'; + var moves = Array.isArray(plan.moves) ? plan.moves : []; + + if (moves.length === 0) { + // Single-move balance job + var src = textOrDash(plan.source_node || plan.source_server); + var dst = textOrDash(plan.target_node || plan.target_server); + var vid = textOrDash(plan.volume_id); + var col = textOrDash(plan.collection); + html += '
' + + '
Volume: ' + escapeHtml(vid) + '
' + + '
Collection: ' + escapeHtml(col) + '
' + + '
Source: ' + escapeHtml(src) + '
' + + '
Target: ' + escapeHtml(dst) + '
' + + '
'; + } else { + // Batch balance job + html += '
' + escapeHtml(String(moves.length)) + ' moves
'; + html += '
' + + ''; + for (var i = 0; i < moves.length; i++) { + var move = moves[i] || {}; + html += '' + + '' + + '' + + '' + + '' + + '' + + ''; + } + html += '
#VolumeSourceTargetCollection
' + escapeHtml(String(i + 1)) + '' + escapeHtml(textOrDash(move.volume_id)) + '' + escapeHtml(textOrDash(move.source_node)) + '' + escapeHtml(textOrDash(move.target_node)) + '' + escapeHtml(textOrDash(move.collection)) + '
'; + } + + html += '
'; + return html; + } + function isActiveJobState(candidateState) { var jobState = candidateState; if (candidateState && typeof candidateState === 'object' && candidateState.state !== undefined) { @@ -1676,9 +1717,15 @@ templ Plugin(page string) { barClass = 'bg-warning'; } + var jobTypeCell = escapeHtml(textOrDash(executionJob.job_type)); + var execLabels = executionJob.labels || {}; + if (execLabels.batch === 'true' && execLabels.batch_size) { + jobTypeCell += ' ' + escapeHtml(execLabels.batch_size) + ' moves'; + } + rows += '' + '' + renderJobLink(executionJob.job_id) + '' + - '' + escapeHtml(textOrDash(executionJob.job_type)) + '' + + '' + jobTypeCell + '' + '' + escapeHtml(textOrDash(executionJob.state)) + '' + '
' + Math.round(progress) + '%
' + '' + escapeHtml(textOrDash(executionJob.worker_id)) + '' + diff --git a/weed/admin/view/app/plugin_templ.go b/weed/admin/view/app/plugin_templ.go index cd9ae58d2..68531c70a 100644 --- a/weed/admin/view/app/plugin_templ.go +++ b/weed/admin/view/app/plugin_templ.go @@ -40,13 +40,13 @@ func Plugin(page string) templ.Component { 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: `view/app/plugin.templ`, Line: 10, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin.templ`, Line: 10, Col: 80} } _, 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, "\">

Workers

Cluster-wide worker status, per-job configuration, detection, queue, and execution workflows.

Workers

0

Active Jobs

0

Activities (recent)

0

Next Run

-

Per Job Type Summary
Job TypeActive JobsRecent Activities
Loading...
Scheduler State
Sequential scheduler with per-job runtime limits
Job TypeEnabledDetectorIn FlightMax RuntimeExec GlobalExec/WorkerExecutor WorkersEffective ExecLast Run
Loading...
Workers
WorkerAddressCapabilitiesLoad
Loading...
Job Type Configuration
Not loaded
Selected Job Type
-
Descriptor
Select a job type to load schema and config.
Admin Config Form
No admin form loaded.
Worker Config Form
No worker form loaded.
Job Scheduling Settings
How often to check for new work.
Next Run
Scheduler
-
Not scheduled
Run History
Keep last 10 success + last 10 errors
Successful Runs
TimeJob IDWorkerDuration
No data
Error Runs
TimeJob IDWorkerError
No data
Detection Results
Run detection to see proposals.
Job Queue
States: pending/assigned/running
Job IDTypeStateProgressWorkerUpdatedMessage
Loading...
Detection Jobs
Detection activities for selected job type
TimeJob TypeRequest IDWorkerStageSourceMessage
Loading...
Execution Jobs
Job IDTypeStateProgressWorkerUpdatedMessage
Loading...
Execution Activities
Non-detection events only
TimeJob TypeJob IDSourceStageMessage
Loading...
Job Detail
Select a job to view details.
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">

Workers

Cluster-wide worker status, per-job configuration, detection, queue, and execution workflows.

Workers

0

Active Jobs

0

Activities (recent)

0

Next Run

-

Per Job Type Summary
Job TypeActive JobsRecent Activities
Loading...
Scheduler State
Sequential scheduler with per-job runtime limits
Job TypeEnabledDetectorIn FlightMax RuntimeExec GlobalExec/WorkerExecutor WorkersEffective ExecLast Run
Loading...
Workers
WorkerAddressCapabilitiesLoad
Loading...
Job Type Configuration
Not loaded
Selected Job Type
-
Descriptor
Select a job type to load schema and config.
Admin Config Form
No admin form loaded.
Worker Config Form
No worker form loaded.
Job Scheduling Settings
How often to check for new work.
Next Run
Scheduler
-
Not scheduled
Run History
Keep last 10 success + last 10 errors
Successful Runs
TimeJob IDWorkerDuration
No data
Error Runs
TimeJob IDWorkerError
No data
Detection Results
Run detection to see proposals.
Job Queue
States: pending/assigned/running
Job IDTypeStateProgressWorkerUpdatedMessage
Loading...
Detection Jobs
Detection activities for selected job type
TimeJob TypeRequest IDWorkerStageSourceMessage
Loading...
Execution Jobs
Job IDTypeStateProgressWorkerUpdatedMessage
Loading...
Execution Activities
Non-detection events only
TimeJob TypeJob IDSourceStageMessage
Loading...
Job Detail
Select a job to view details.
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }