You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							429 lines
						
					
					
						
							21 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							429 lines
						
					
					
						
							21 KiB
						
					
					
				| package app | |
| 
 | |
| import ( | |
|     "fmt" | |
|     "time" | |
|     "github.com/seaweedfs/seaweedfs/weed/admin/maintenance" | |
| ) | |
| 
 | |
| templ MaintenanceQueue(data *maintenance.MaintenanceQueueData) { | |
|     <div class="container-fluid"> | |
|         <!-- Header --> | |
|         <div class="row mb-4"> | |
|             <div class="col-12"> | |
|                 <div class="d-flex justify-content-between align-items-center"> | |
|                     <h2 class="mb-0"> | |
|                         <i class="fas fa-tasks me-2"></i> | |
|                         Maintenance Queue | |
|                     </h2> | |
|                     <div class="btn-group"> | |
|                         <button type="button" class="btn btn-primary" onclick="triggerScan()"> | |
|                             <i class="fas fa-search me-1"></i> | |
|                             Trigger Scan | |
|                         </button> | |
|                         <button type="button" class="btn btn-secondary" onclick="refreshPage()"> | |
|                             <i class="fas fa-sync-alt me-1"></i> | |
|                             Refresh | |
|                         </button> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Statistics Cards --> | |
|         <div class="row mb-4"> | |
|             <div class="col-md-3"> | |
|                 <div class="card border-primary"> | |
|                     <div class="card-body text-center"> | |
|                         <i class="fas fa-clock fa-2x text-primary mb-2"></i> | |
|                         <h4 class="mb-1">{fmt.Sprintf("%d", data.Stats.PendingTasks)}</h4> | |
|                         <p class="text-muted mb-0">Pending Tasks</p> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|             <div class="col-md-3"> | |
|                 <div class="card border-warning"> | |
|                     <div class="card-body text-center"> | |
|                         <i class="fas fa-running fa-2x text-warning mb-2"></i> | |
|                         <h4 class="mb-1">{fmt.Sprintf("%d", data.Stats.RunningTasks)}</h4> | |
|                         <p class="text-muted mb-0">Running Tasks</p> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|             <div class="col-md-3"> | |
|                 <div class="card border-success"> | |
|                     <div class="card-body text-center"> | |
|                         <i class="fas fa-check-circle fa-2x text-success mb-2"></i> | |
|                         <h4 class="mb-1">{fmt.Sprintf("%d", data.Stats.CompletedToday)}</h4> | |
|                         <p class="text-muted mb-0">Completed Today</p> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|             <div class="col-md-3"> | |
|                 <div class="card border-danger"> | |
|                     <div class="card-body text-center"> | |
|                         <i class="fas fa-exclamation-triangle fa-2x text-danger mb-2"></i> | |
|                         <h4 class="mb-1">{fmt.Sprintf("%d", data.Stats.FailedToday)}</h4> | |
|                         <p class="text-muted mb-0">Failed Today</p> | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Completed Tasks --> | |
|         <div class="row mb-4"> | |
|             <div class="col-12"> | |
|                 <div class="card"> | |
|                     <div class="card-header bg-success text-white"> | |
|                         <h5 class="mb-0"> | |
|                             <i class="fas fa-check-circle me-2"></i> | |
|                             Completed Tasks | |
|                         </h5> | |
|                     </div> | |
|                     <div class="card-body"> | |
|                         if data.Stats.CompletedToday == 0 && data.Stats.FailedToday == 0 { | |
|                             <div class="text-center text-muted py-4"> | |
|                                 <i class="fas fa-check-circle fa-3x mb-3"></i> | |
|                                 <p>No completed maintenance tasks today</p> | |
|                                 <small>Completed tasks will appear here after workers finish processing them</small> | |
|                             </div> | |
|                         } else { | |
|                             <div class="table-responsive"> | |
|                                 <table class="table table-hover"> | |
|                                     <thead> | |
|                                         <tr> | |
|                                             <th>Type</th> | |
|                                             <th>Status</th> | |
|                                             <th>Volume</th> | |
|                                             <th>Worker</th> | |
|                                             <th>Duration</th> | |
|                                             <th>Completed</th> | |
|                                         </tr> | |
|                                     </thead> | |
|                                     <tbody> | |
|                                         for _, task := range data.Tasks { | |
|                                             if string(task.Status) == "completed" || string(task.Status) == "failed" || string(task.Status) == "cancelled" { | |
|                                                 if string(task.Status) == "failed" { | |
|                                                     <tr class="table-danger clickable-row" data-task-id={task.ID} onclick="navigateToTask(this)" style="cursor: pointer;"> | |
|                                                         <td> | |
|                                                             @TaskTypeIcon(task.Type) | |
|                                                             {string(task.Type)} | |
|                                                         </td> | |
|                                                         <td>@StatusBadge(task.Status)</td> | |
|                                                         <td>{fmt.Sprintf("%d", task.VolumeID)}</td> | |
|                                                         <td> | |
|                                                             if task.WorkerID != "" { | |
|                                                                 <small>{task.WorkerID}</small> | |
|                                                             } else { | |
|                                                                 <span class="text-muted">-</span> | |
|                                                             } | |
|                                                         </td> | |
|                                                         <td> | |
|                                                             if task.StartedAt != nil && task.CompletedAt != nil { | |
|                                                                 {formatDuration(task.CompletedAt.Sub(*task.StartedAt))} | |
|                                                             } else { | |
|                                                                 <span class="text-muted">-</span> | |
|                                                             } | |
|                                                         </td> | |
|                                                         <td> | |
|                                                             if task.CompletedAt != nil { | |
|                                                                 {task.CompletedAt.Format("2006-01-02 15:04")} | |
|                                                             } else { | |
|                                                                 <span class="text-muted">-</span> | |
|                                                             } | |
|                                                         </td> | |
|                                                     </tr> | |
|                                                 } else { | |
|                                                     <tr class="clickable-row" data-task-id={task.ID} onclick="navigateToTask(this)" style="cursor: pointer;"> | |
|                                                         <td> | |
|                                                             @TaskTypeIcon(task.Type) | |
|                                                             {string(task.Type)} | |
|                                                         </td> | |
|                                                         <td>@StatusBadge(task.Status)</td> | |
|                                                         <td>{fmt.Sprintf("%d", task.VolumeID)}</td> | |
|                                                         <td> | |
|                                                             if task.WorkerID != "" { | |
|                                                                 <small>{task.WorkerID}</small> | |
|                                                             } else { | |
|                                                                 <span class="text-muted">-</span> | |
|                                                             } | |
|                                                         </td> | |
|                                                         <td> | |
|                                                             if task.StartedAt != nil && task.CompletedAt != nil { | |
|                                                                 {formatDuration(task.CompletedAt.Sub(*task.StartedAt))} | |
|                                                             } else { | |
|                                                                 <span class="text-muted">-</span> | |
|                                                             } | |
|                                                         </td> | |
|                                                         <td> | |
|                                                             if task.CompletedAt != nil { | |
|                                                                 {task.CompletedAt.Format("2006-01-02 15:04")} | |
|                                                             } else { | |
|                                                                 <span class="text-muted">-</span> | |
|                                                             } | |
|                                                         </td> | |
|                                                     </tr> | |
|                                                 } | |
|                                             } | |
|                                         } | |
|                                     </tbody> | |
|                                 </table> | |
|                             </div> | |
|                         } | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Pending Tasks --> | |
|         <div class="row mb-4"> | |
|             <div class="col-12"> | |
|                 <div class="card"> | |
|                     <div class="card-header bg-primary text-white"> | |
|                         <h5 class="mb-0"> | |
|                             <i class="fas fa-clock me-2"></i> | |
|                             Pending Tasks | |
|                         </h5> | |
|                     </div> | |
|                     <div class="card-body"> | |
|                         if data.Stats.PendingTasks == 0 { | |
|                             <div class="text-center text-muted py-4"> | |
|                                 <i class="fas fa-clipboard-list fa-3x mb-3"></i> | |
|                                 <p>No pending maintenance tasks</p> | |
|                                 <small>Pending tasks will appear here when the system detects maintenance needs</small> | |
|                             </div> | |
|                         } else { | |
|                             <div class="table-responsive"> | |
|                                 <table class="table table-hover"> | |
|                                     <thead> | |
|                                         <tr> | |
|                                             <th>Type</th> | |
|                                             <th>Priority</th> | |
|                                             <th>Volume</th> | |
|                                             <th>Server</th> | |
|                                             <th>Reason</th> | |
|                                             <th>Created</th> | |
|                                         </tr> | |
|                                     </thead> | |
|                                     <tbody> | |
|                                         for _, task := range data.Tasks { | |
|                                             if string(task.Status) == "pending" { | |
|                                                 <tr class="clickable-row" data-task-id={task.ID} onclick="navigateToTask(this)" style="cursor: pointer;"> | |
|                                                     <td> | |
|                                                         @TaskTypeIcon(task.Type) | |
|                                                         {string(task.Type)} | |
|                                                     </td> | |
|                                                     <td>@PriorityBadge(task.Priority)</td> | |
|                                                     <td>{fmt.Sprintf("%d", task.VolumeID)}</td> | |
|                                                     <td><small>{task.Server}</small></td> | |
|                                                     <td><small>{task.Reason}</small></td> | |
|                                                     <td>{task.CreatedAt.Format("2006-01-02 15:04")}</td> | |
|                                                 </tr> | |
|                                             } | |
|                                         } | |
|                                     </tbody> | |
|                                 </table> | |
|                             </div> | |
|                         } | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
| 
 | |
|         <!-- Active Tasks --> | |
|         <div class="row mb-4"> | |
|             <div class="col-12"> | |
|                 <div class="card"> | |
|                     <div class="card-header bg-warning text-dark"> | |
|                         <h5 class="mb-0"> | |
|                             <i class="fas fa-running me-2"></i> | |
|                             Active Tasks | |
|                         </h5> | |
|                     </div> | |
|                     <div class="card-body"> | |
|                         if data.Stats.RunningTasks == 0 { | |
|                             <div class="text-center text-muted py-4"> | |
|                                 <i class="fas fa-tasks fa-3x mb-3"></i> | |
|                                 <p>No active maintenance tasks</p> | |
|                                 <small>Active tasks will appear here when workers start processing them</small> | |
|                             </div> | |
|                         } else { | |
|                             <div class="table-responsive"> | |
|                                 <table class="table table-hover"> | |
|                                     <thead> | |
|                                         <tr> | |
|                                             <th>Type</th> | |
|                                             <th>Status</th> | |
|                                             <th>Progress</th> | |
|                                             <th>Volume</th> | |
|                                             <th>Worker</th> | |
|                                             <th>Started</th> | |
|                                         </tr> | |
|                                     </thead> | |
|                                     <tbody> | |
|                                         for _, task := range data.Tasks { | |
|                                             if string(task.Status) == "assigned" || string(task.Status) == "in_progress" { | |
|                                                 <tr class="clickable-row" data-task-id={task.ID} onclick="navigateToTask(this)" style="cursor: pointer;"> | |
|                                                     <td> | |
|                                                         @TaskTypeIcon(task.Type) | |
|                                                         {string(task.Type)} | |
|                                                     </td> | |
|                                                     <td>@StatusBadge(task.Status)</td> | |
|                                                     <td>@ProgressBar(task.Progress, task.Status)</td> | |
|                                                     <td>{fmt.Sprintf("%d", task.VolumeID)}</td> | |
|                                                     <td> | |
|                                                         if task.WorkerID != "" { | |
|                                                             <small>{task.WorkerID}</small> | |
|                                                         } else { | |
|                                                             <span class="text-muted">-</span> | |
|                                                         } | |
|                                                     </td> | |
|                                                     <td> | |
|                                                         if task.StartedAt != nil { | |
|                                                             {task.StartedAt.Format("2006-01-02 15:04")} | |
|                                                         } else { | |
|                                                             <span class="text-muted">-</span> | |
|                                                         } | |
|                                                     </td> | |
|                                                 </tr> | |
|                                             } | |
|                                         } | |
|                                     </tbody> | |
|                                 </table> | |
|                             </div> | |
|                         } | |
|                     </div> | |
|                 </div> | |
|             </div> | |
|         </div> | |
|     </div> | |
| 
 | |
|     <script> | |
|         // Debug output to browser console | |
|         console.log("DEBUG: Maintenance Queue Template loaded"); | |
|          | |
|         // Auto-refresh every 10 seconds | |
|         setInterval(function() { | |
|             if (!document.hidden) { | |
|                 window.location.reload(); | |
|             } | |
|         }, 10000); | |
| 
 | |
|         window.triggerScan = function() { | |
|             console.log("triggerScan called"); | |
|             fetch('/api/maintenance/scan', { | |
|                 method: 'POST', | |
|                 headers: { | |
|                     'Content-Type': 'application/json', | |
|                 } | |
|             }) | |
|             .then(response => response.json()) | |
|             .then(data => { | |
|                 if (data.success) { | |
|                     alert('Maintenance scan triggered successfully'); | |
|                     setTimeout(() => window.location.reload(), 2000); | |
|                 } else { | |
|                     alert('Failed to trigger scan: ' + (data.error || 'Unknown error')); | |
|                 } | |
|             }) | |
|             .catch(error => { | |
|                 alert('Error: ' + error.message); | |
|             }); | |
|         }; | |
| 
 | |
|         window.refreshPage = function() { | |
|             console.log("refreshPage called"); | |
|             window.location.reload(); | |
|         }; | |
| 
 | |
|         window.navigateToTask = function(element) { | |
|             const taskId = element.getAttribute('data-task-id'); | |
|             if (taskId) { | |
|                 window.location.href = '/maintenance/tasks/' + taskId; | |
|             } | |
|         }; | |
|     </script> | |
| } | |
| 
 | |
| // Helper components | |
| templ TaskTypeIcon(taskType maintenance.MaintenanceTaskType) { | |
|     <i class={maintenance.GetTaskIcon(taskType) + " me-1"}></i> | |
| } | |
| 
 | |
| templ PriorityBadge(priority maintenance.MaintenanceTaskPriority) { | |
|     switch priority { | |
|     case maintenance.PriorityCritical: | |
|         <span class="badge bg-danger">Critical</span> | |
|     case maintenance.PriorityHigh: | |
|         <span class="badge bg-warning">High</span> | |
|     case maintenance.PriorityNormal: | |
|         <span class="badge bg-primary">Normal</span> | |
|     case maintenance.PriorityLow: | |
|         <span class="badge bg-secondary">Low</span> | |
|     default: | |
|         <span class="badge bg-light text-dark">Unknown</span> | |
|     } | |
| } | |
| 
 | |
| templ StatusBadge(status maintenance.MaintenanceTaskStatus) { | |
|     switch status { | |
|     case maintenance.TaskStatusPending: | |
|         <span class="badge bg-secondary">Pending</span> | |
|     case maintenance.TaskStatusAssigned: | |
|         <span class="badge bg-info">Assigned</span> | |
|     case maintenance.TaskStatusInProgress: | |
|         <span class="badge bg-warning">Running</span> | |
|     case maintenance.TaskStatusCompleted: | |
|         <span class="badge bg-success">Completed</span> | |
|     case maintenance.TaskStatusFailed: | |
|         <span class="badge bg-danger">Failed</span> | |
|     case maintenance.TaskStatusCancelled: | |
|         <span class="badge bg-light text-dark">Cancelled</span> | |
|     default: | |
|         <span class="badge bg-light text-dark">Unknown</span> | |
|     } | |
| } | |
| 
 | |
| templ ProgressBar(progress float64, status maintenance.MaintenanceTaskStatus) { | |
|     if status == maintenance.TaskStatusInProgress || status == maintenance.TaskStatusAssigned { | |
|         <div class="progress" style="height: 8px; min-width: 100px;"> | |
|             <div class="progress-bar" role="progressbar" style={fmt.Sprintf("width: %.1f%%", progress)}> | |
|             </div> | |
|         </div> | |
|         <small class="text-muted">{fmt.Sprintf("%.1f%%", progress)}</small> | |
|     } else if status == maintenance.TaskStatusCompleted { | |
|         <div class="progress" style="height: 8px; min-width: 100px;"> | |
|             <div class="progress-bar bg-success" role="progressbar" style="width: 100%"> | |
|             </div> | |
|         </div> | |
|         <small class="text-success">100%</small> | |
|     } else { | |
|         <span class="text-muted">-</span> | |
|     } | |
| } | |
| 
 | |
| func formatDuration(d time.Duration) string { | |
|     if d < time.Minute { | |
|         return fmt.Sprintf("%.0fs", d.Seconds()) | |
|     } else if d < time.Hour { | |
|         return fmt.Sprintf("%.1fm", d.Minutes()) | |
|     } else { | |
|         return fmt.Sprintf("%.1fh", d.Hours()) | |
|     } | |
| } | |
| 
 | |
| func formatTimeAgo(t time.Time) string { | |
|     duration := time.Since(t) | |
|     if duration < time.Minute { | |
|         return "just now" | |
|     } else if duration < time.Hour { | |
|         minutes := int(duration.Minutes()) | |
|         return fmt.Sprintf("%dm ago", minutes) | |
|     } else if duration < 24*time.Hour { | |
|         hours := int(duration.Hours()) | |
|         return fmt.Sprintf("%dh ago", hours) | |
|     } else { | |
|         days := int(duration.Hours() / 24) | |
|         return fmt.Sprintf("%dd ago", days) | |
|     } | |
| }  |