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.
		
		
		
		
		
			
		
			
				
					
					
						
							589 lines
						
					
					
						
							19 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							589 lines
						
					
					
						
							19 KiB
						
					
					
				| package handlers | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"net/http" | |
| 	"reflect" | |
| 	"strconv" | |
| 	"strings" | |
| 	"time" | |
| 
 | |
| 	"github.com/gin-gonic/gin" | |
| 	"github.com/seaweedfs/seaweedfs/weed/admin/config" | |
| 	"github.com/seaweedfs/seaweedfs/weed/admin/dash" | |
| 	"github.com/seaweedfs/seaweedfs/weed/admin/maintenance" | |
| 	"github.com/seaweedfs/seaweedfs/weed/admin/view/app" | |
| 	"github.com/seaweedfs/seaweedfs/weed/admin/view/layout" | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/tasks" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/tasks/balance" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/tasks/erasure_coding" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/tasks/vacuum" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/types" | |
| ) | |
| 
 | |
| // MaintenanceHandlers handles maintenance-related HTTP requests | |
| type MaintenanceHandlers struct { | |
| 	adminServer *dash.AdminServer | |
| } | |
| 
 | |
| // NewMaintenanceHandlers creates a new instance of MaintenanceHandlers | |
| func NewMaintenanceHandlers(adminServer *dash.AdminServer) *MaintenanceHandlers { | |
| 	return &MaintenanceHandlers{ | |
| 		adminServer: adminServer, | |
| 	} | |
| } | |
| 
 | |
| // ShowTaskDetail displays the task detail page | |
| func (h *MaintenanceHandlers) ShowTaskDetail(c *gin.Context) { | |
| 	taskID := c.Param("id") | |
| 
 | |
| 	taskDetail, err := h.adminServer.GetMaintenanceTaskDetail(taskID) | |
| 	if err != nil { | |
| 		glog.Errorf("DEBUG ShowTaskDetail: error getting task detail for %s: %v", taskID, err) | |
| 		c.String(http.StatusNotFound, "Task not found: %s (Error: %v)", taskID, err) | |
| 		return | |
| 	} | |
| 
 | |
| 
 | |
| 	c.Header("Content-Type", "text/html") | |
| 	taskDetailComponent := app.TaskDetail(taskDetail) | |
| 	layoutComponent := layout.Layout(c, taskDetailComponent) | |
| 	err = layoutComponent.Render(c.Request.Context(), c.Writer) | |
| 	if err != nil { | |
| 		glog.Errorf("DEBUG ShowTaskDetail: render error: %v", err) | |
| 		c.String(http.StatusInternalServerError, "Failed to render template: %v", err) | |
| 		return | |
| 	} | |
| 
 | |
| } | |
| 
 | |
| // ShowMaintenanceQueue displays the maintenance queue page | |
| func (h *MaintenanceHandlers) ShowMaintenanceQueue(c *gin.Context) { | |
| 	// Add timeout to prevent hanging | |
| 	ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) | |
| 	defer cancel() | |
| 
 | |
| 	// Use a channel to handle timeout for data retrieval | |
| 	type result struct { | |
| 		data *maintenance.MaintenanceQueueData | |
| 		err  error | |
| 	} | |
| 	resultChan := make(chan result, 1) | |
| 
 | |
| 	go func() { | |
| 		data, err := h.getMaintenanceQueueData() | |
| 		resultChan <- result{data: data, err: err} | |
| 	}() | |
| 
 | |
| 	select { | |
| 	case res := <-resultChan: | |
| 		if res.err != nil { | |
| 			glog.V(1).Infof("ShowMaintenanceQueue: error getting data: %v", res.err) | |
| 			c.JSON(http.StatusInternalServerError, gin.H{"error": res.err.Error()}) | |
| 			return | |
| 		} | |
| 
 | |
| 		glog.V(2).Infof("ShowMaintenanceQueue: got data with %d tasks", len(res.data.Tasks)) | |
| 
 | |
| 		// Render HTML template | |
| 		c.Header("Content-Type", "text/html") | |
| 		maintenanceComponent := app.MaintenanceQueue(res.data) | |
| 		layoutComponent := layout.Layout(c, maintenanceComponent) | |
| 		err := layoutComponent.Render(ctx, c.Writer) | |
| 		if err != nil { | |
| 			glog.V(1).Infof("ShowMaintenanceQueue: render error: %v", err) | |
| 			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()}) | |
| 			return | |
| 		} | |
| 
 | |
| 		glog.V(3).Infof("ShowMaintenanceQueue: template rendered successfully") | |
| 
 | |
| 	case <-ctx.Done(): | |
| 		glog.Warningf("ShowMaintenanceQueue: timeout waiting for data") | |
| 		c.JSON(http.StatusRequestTimeout, gin.H{ | |
| 			"error":      "Request timeout - maintenance data retrieval took too long. This may indicate a system issue.", | |
| 			"suggestion": "Try refreshing the page or contact system administrator if the problem persists.", | |
| 		}) | |
| 		return | |
| 	} | |
| } | |
| 
 | |
| // ShowMaintenanceWorkers displays the maintenance workers page | |
| func (h *MaintenanceHandlers) ShowMaintenanceWorkers(c *gin.Context) { | |
| 	workersData, err := h.adminServer.GetMaintenanceWorkersData() | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Render HTML template | |
| 	c.Header("Content-Type", "text/html") | |
| 	workersComponent := app.MaintenanceWorkers(workersData) | |
| 	layoutComponent := layout.Layout(c, workersComponent) | |
| 	err = layoutComponent.Render(c.Request.Context(), c.Writer) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()}) | |
| 		return | |
| 	} | |
| } | |
| 
 | |
| // ShowMaintenanceConfig displays the maintenance configuration page | |
| func (h *MaintenanceHandlers) ShowMaintenanceConfig(c *gin.Context) { | |
| 	config, err := h.getMaintenanceConfig() | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Get the schema for dynamic form rendering | |
| 	schema := maintenance.GetMaintenanceConfigSchema() | |
| 
 | |
| 	// Render HTML template using schema-driven approach | |
| 	c.Header("Content-Type", "text/html") | |
| 	configComponent := app.MaintenanceConfigSchema(config, schema) | |
| 	layoutComponent := layout.Layout(c, configComponent) | |
| 	err = layoutComponent.Render(c.Request.Context(), c.Writer) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()}) | |
| 		return | |
| 	} | |
| } | |
| 
 | |
| // ShowTaskConfig displays the configuration page for a specific task type | |
| func (h *MaintenanceHandlers) ShowTaskConfig(c *gin.Context) { | |
| 	taskTypeName := c.Param("taskType") | |
| 
 | |
| 	// Get the schema for this task type | |
| 	schema := tasks.GetTaskConfigSchema(taskTypeName) | |
| 	if schema == nil { | |
| 		c.JSON(http.StatusNotFound, gin.H{"error": "Task type not found or no schema available"}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Get the UI provider for current configuration | |
| 	uiRegistry := tasks.GetGlobalUIRegistry() | |
| 	typesRegistry := tasks.GetGlobalTypesRegistry() | |
| 
 | |
| 	var provider types.TaskUIProvider | |
| 	for workerTaskType := range typesRegistry.GetAllDetectors() { | |
| 		if string(workerTaskType) == taskTypeName { | |
| 			provider = uiRegistry.GetProvider(workerTaskType) | |
| 			break | |
| 		} | |
| 	} | |
| 
 | |
| 	if provider == nil { | |
| 		c.JSON(http.StatusNotFound, gin.H{"error": "UI provider not found for task type"}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Get current configuration | |
| 	currentConfig := provider.GetCurrentConfig() | |
| 
 | |
| 	// Note: Do NOT apply schema defaults to current config as it overrides saved values | |
| 	// Only apply defaults when creating new configs, not when displaying existing ones | |
|  | |
| 	// Create task configuration data | |
| 	configData := &maintenance.TaskConfigData{ | |
| 		TaskType:    maintenance.MaintenanceTaskType(taskTypeName), | |
| 		TaskName:    schema.DisplayName, | |
| 		TaskIcon:    schema.Icon, | |
| 		Description: schema.Description, | |
| 	} | |
| 
 | |
| 	// Render HTML template using schema-based approach | |
| 	c.Header("Content-Type", "text/html") | |
| 	taskConfigComponent := app.TaskConfigSchema(configData, schema, currentConfig) | |
| 	layoutComponent := layout.Layout(c, taskConfigComponent) | |
| 	err := layoutComponent.Render(c.Request.Context(), c.Writer) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()}) | |
| 		return | |
| 	} | |
| } | |
| 
 | |
| // UpdateTaskConfig updates task configuration from form | |
| func (h *MaintenanceHandlers) UpdateTaskConfig(c *gin.Context) { | |
| 	taskTypeName := c.Param("taskType") | |
| 	taskType := types.TaskType(taskTypeName) | |
| 
 | |
| 	// Parse form data | |
| 	err := c.Request.ParseForm() | |
| 	if err != nil { | |
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse form data: " + err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Debug logging - show received form data | |
| 	glog.V(1).Infof("Received form data for task type %s:", taskTypeName) | |
| 	for key, values := range c.Request.PostForm { | |
| 		glog.V(1).Infof("  %s: %v", key, values) | |
| 	} | |
| 
 | |
| 	// Get the task configuration schema | |
| 	schema := tasks.GetTaskConfigSchema(taskTypeName) | |
| 	if schema == nil { | |
| 		c.JSON(http.StatusNotFound, gin.H{"error": "Schema not found for task type: " + taskTypeName}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Create a new config instance based on task type and apply schema defaults | |
| 	var config TaskConfig | |
| 	switch taskType { | |
| 	case types.TaskTypeVacuum: | |
| 		config = &vacuum.Config{} | |
| 	case types.TaskTypeBalance: | |
| 		config = &balance.Config{} | |
| 	case types.TaskTypeErasureCoding: | |
| 		config = &erasure_coding.Config{} | |
| 	default: | |
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported task type: " + taskTypeName}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Apply schema defaults first using type-safe method | |
| 	if err := schema.ApplyDefaultsToConfig(config); err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to apply defaults: " + err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// First, get the current configuration to preserve existing values | |
| 	currentUIRegistry := tasks.GetGlobalUIRegistry() | |
| 	currentTypesRegistry := tasks.GetGlobalTypesRegistry() | |
| 
 | |
| 	var currentProvider types.TaskUIProvider | |
| 	for workerTaskType := range currentTypesRegistry.GetAllDetectors() { | |
| 		if string(workerTaskType) == string(taskType) { | |
| 			currentProvider = currentUIRegistry.GetProvider(workerTaskType) | |
| 			break | |
| 		} | |
| 	} | |
| 
 | |
| 	if currentProvider != nil { | |
| 		// Copy current config values to the new config | |
| 		currentConfig := currentProvider.GetCurrentConfig() | |
| 		if currentConfigProtobuf, ok := currentConfig.(TaskConfig); ok { | |
| 			// Apply current values using protobuf directly - no map conversion needed! | |
| 			currentPolicy := currentConfigProtobuf.ToTaskPolicy() | |
| 			if err := config.FromTaskPolicy(currentPolicy); err != nil { | |
| 				glog.Warningf("Failed to load current config for %s: %v", taskTypeName, err) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	// Parse form data using schema-based approach (this will override with new values) | |
| 	err = h.parseTaskConfigFromForm(c.Request.PostForm, schema, config) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse configuration: " + err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Debug logging - show parsed config values | |
| 	switch taskType { | |
| 	case types.TaskTypeVacuum: | |
| 		if vacuumConfig, ok := config.(*vacuum.Config); ok { | |
| 			glog.V(1).Infof("Parsed vacuum config - GarbageThreshold: %f, MinVolumeAgeSeconds: %d, MinIntervalSeconds: %d", | |
| 				vacuumConfig.GarbageThreshold, vacuumConfig.MinVolumeAgeSeconds, vacuumConfig.MinIntervalSeconds) | |
| 		} | |
| 	case types.TaskTypeErasureCoding: | |
| 		if ecConfig, ok := config.(*erasure_coding.Config); ok { | |
| 			glog.V(1).Infof("Parsed EC config - FullnessRatio: %f, QuietForSeconds: %d, MinSizeMB: %d, CollectionFilter: '%s'", | |
| 				ecConfig.FullnessRatio, ecConfig.QuietForSeconds, ecConfig.MinSizeMB, ecConfig.CollectionFilter) | |
| 		} | |
| 	case types.TaskTypeBalance: | |
| 		if balanceConfig, ok := config.(*balance.Config); ok { | |
| 			glog.V(1).Infof("Parsed balance config - Enabled: %v, MaxConcurrent: %d, ScanIntervalSeconds: %d, ImbalanceThreshold: %f, MinServerCount: %d", | |
| 				balanceConfig.Enabled, balanceConfig.MaxConcurrent, balanceConfig.ScanIntervalSeconds, balanceConfig.ImbalanceThreshold, balanceConfig.MinServerCount) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Validate the configuration | |
| 	if validationErrors := schema.ValidateConfig(config); len(validationErrors) > 0 { | |
| 		errorMessages := make([]string, len(validationErrors)) | |
| 		for i, err := range validationErrors { | |
| 			errorMessages[i] = err.Error() | |
| 		} | |
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "Configuration validation failed", "details": errorMessages}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Apply configuration using UIProvider | |
| 	uiRegistry := tasks.GetGlobalUIRegistry() | |
| 	typesRegistry := tasks.GetGlobalTypesRegistry() | |
| 
 | |
| 	var provider types.TaskUIProvider | |
| 	for workerTaskType := range typesRegistry.GetAllDetectors() { | |
| 		if string(workerTaskType) == string(taskType) { | |
| 			provider = uiRegistry.GetProvider(workerTaskType) | |
| 			break | |
| 		} | |
| 	} | |
| 
 | |
| 	if provider == nil { | |
| 		c.JSON(http.StatusNotFound, gin.H{"error": "UI provider not found for task type"}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Apply configuration using provider | |
| 	err = provider.ApplyTaskConfig(config) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to apply configuration: " + err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Save task configuration to protobuf file using ConfigPersistence | |
| 	if h.adminServer != nil && h.adminServer.GetConfigPersistence() != nil { | |
| 		err = h.saveTaskConfigToProtobuf(taskType, config) | |
| 		if err != nil { | |
| 			glog.Warningf("Failed to save task config to protobuf file: %v", err) | |
| 			// Don't fail the request, just log the warning | |
| 		} | |
| 	} | |
| 
 | |
| 	// Trigger a configuration reload in the maintenance manager | |
| 	if h.adminServer != nil { | |
| 		if manager := h.adminServer.GetMaintenanceManager(); manager != nil { | |
| 			err = manager.ReloadTaskConfigurations() | |
| 			if err != nil { | |
| 				glog.Warningf("Failed to reload task configurations: %v", err) | |
| 			} else { | |
| 				glog.V(1).Infof("Successfully reloaded task configurations after updating %s", taskTypeName) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	// Redirect back to task configuration page | |
| 	c.Redirect(http.StatusSeeOther, "/maintenance/config/"+taskTypeName) | |
| } | |
| 
 | |
| // parseTaskConfigFromForm parses form data using schema definitions | |
| func (h *MaintenanceHandlers) parseTaskConfigFromForm(formData map[string][]string, schema *tasks.TaskConfigSchema, config interface{}) error { | |
| 	configValue := reflect.ValueOf(config) | |
| 	if configValue.Kind() == reflect.Ptr { | |
| 		configValue = configValue.Elem() | |
| 	} | |
| 
 | |
| 	if configValue.Kind() != reflect.Struct { | |
| 		return fmt.Errorf("config must be a struct or pointer to struct") | |
| 	} | |
| 
 | |
| 	configType := configValue.Type() | |
| 
 | |
| 	for i := 0; i < configValue.NumField(); i++ { | |
| 		field := configValue.Field(i) | |
| 		fieldType := configType.Field(i) | |
| 
 | |
| 		// Handle embedded structs recursively | |
| 		if fieldType.Anonymous && field.Kind() == reflect.Struct { | |
| 			err := h.parseTaskConfigFromForm(formData, schema, field.Addr().Interface()) | |
| 			if err != nil { | |
| 				return fmt.Errorf("error parsing embedded struct %s: %w", fieldType.Name, err) | |
| 			} | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Get JSON tag name | |
| 		jsonTag := fieldType.Tag.Get("json") | |
| 		if jsonTag == "" { | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Remove options like ",omitempty" | |
| 		if commaIdx := strings.Index(jsonTag, ","); commaIdx > 0 { | |
| 			jsonTag = jsonTag[:commaIdx] | |
| 		} | |
| 
 | |
| 		// Find corresponding schema field | |
| 		schemaField := schema.GetFieldByName(jsonTag) | |
| 		if schemaField == nil { | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Parse value based on field type | |
| 		if err := h.parseFieldFromForm(formData, schemaField, field); err != nil { | |
| 			return fmt.Errorf("error parsing field %s: %w", schemaField.DisplayName, err) | |
| 		} | |
| 	} | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // parseFieldFromForm parses a single field value from form data | |
| func (h *MaintenanceHandlers) parseFieldFromForm(formData map[string][]string, schemaField *config.Field, fieldValue reflect.Value) error { | |
| 	if !fieldValue.CanSet() { | |
| 		return nil | |
| 	} | |
| 
 | |
| 	switch schemaField.Type { | |
| 	case config.FieldTypeBool: | |
| 		// Checkbox fields - present means true, absent means false | |
| 		_, exists := formData[schemaField.JSONName] | |
| 		fieldValue.SetBool(exists) | |
| 
 | |
| 	case config.FieldTypeInt: | |
| 		if values, ok := formData[schemaField.JSONName]; ok && len(values) > 0 { | |
| 			if intVal, err := strconv.Atoi(values[0]); err != nil { | |
| 				return fmt.Errorf("invalid integer value: %s", values[0]) | |
| 			} else { | |
| 				fieldValue.SetInt(int64(intVal)) | |
| 			} | |
| 		} | |
| 
 | |
| 	case config.FieldTypeFloat: | |
| 		if values, ok := formData[schemaField.JSONName]; ok && len(values) > 0 { | |
| 			if floatVal, err := strconv.ParseFloat(values[0], 64); err != nil { | |
| 				return fmt.Errorf("invalid float value: %s", values[0]) | |
| 			} else { | |
| 				fieldValue.SetFloat(floatVal) | |
| 			} | |
| 		} | |
| 
 | |
| 	case config.FieldTypeString: | |
| 		if values, ok := formData[schemaField.JSONName]; ok && len(values) > 0 { | |
| 			fieldValue.SetString(values[0]) | |
| 		} | |
| 
 | |
| 	case config.FieldTypeInterval: | |
| 		// Parse interval fields with value + unit | |
| 		valueKey := schemaField.JSONName + "_value" | |
| 		unitKey := schemaField.JSONName + "_unit" | |
| 
 | |
| 		if valueStrs, ok := formData[valueKey]; ok && len(valueStrs) > 0 { | |
| 			value, err := strconv.Atoi(valueStrs[0]) | |
| 			if err != nil { | |
| 				return fmt.Errorf("invalid interval value: %s", valueStrs[0]) | |
| 			} | |
| 
 | |
| 			unit := "minutes" // default | |
| 			if unitStrs, ok := formData[unitKey]; ok && len(unitStrs) > 0 { | |
| 				unit = unitStrs[0] | |
| 			} | |
| 
 | |
| 			// Convert to seconds | |
| 			seconds := config.IntervalValueUnitToSeconds(value, unit) | |
| 			fieldValue.SetInt(int64(seconds)) | |
| 		} | |
| 
 | |
| 	default: | |
| 		return fmt.Errorf("unsupported field type: %s", schemaField.Type) | |
| 	} | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // UpdateMaintenanceConfig updates maintenance configuration from form | |
| func (h *MaintenanceHandlers) UpdateMaintenanceConfig(c *gin.Context) { | |
| 	var config maintenance.MaintenanceConfig | |
| 	if err := c.ShouldBind(&config); err != nil { | |
| 		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	err := h.updateMaintenanceConfig(&config) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	c.Redirect(http.StatusSeeOther, "/maintenance/config") | |
| } | |
| 
 | |
| // Helper methods that delegate to AdminServer | |
|  | |
| func (h *MaintenanceHandlers) getMaintenanceQueueData() (*maintenance.MaintenanceQueueData, error) { | |
| 	tasks, err := h.getMaintenanceTasks() | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	workers, err := h.getMaintenanceWorkers() | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	stats, err := h.getMaintenanceQueueStats() | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	data := &maintenance.MaintenanceQueueData{ | |
| 		Tasks:       tasks, | |
| 		Workers:     workers, | |
| 		Stats:       stats, | |
| 		LastUpdated: time.Now(), | |
| 	} | |
| 
 | |
| 	return data, nil | |
| } | |
| 
 | |
| func (h *MaintenanceHandlers) getMaintenanceQueueStats() (*maintenance.QueueStats, error) { | |
| 	// Use the exported method from AdminServer | |
| 	return h.adminServer.GetMaintenanceQueueStats() | |
| } | |
| 
 | |
| func (h *MaintenanceHandlers) getMaintenanceTasks() ([]*maintenance.MaintenanceTask, error) { | |
| 	// Call the maintenance manager directly to get recent tasks (limit for performance) | |
| 	if h.adminServer == nil { | |
| 		return []*maintenance.MaintenanceTask{}, nil | |
| 	} | |
| 
 | |
| 	manager := h.adminServer.GetMaintenanceManager() | |
| 	if manager == nil { | |
| 		return []*maintenance.MaintenanceTask{}, nil | |
| 	} | |
| 
 | |
| 	// Get recent tasks only (last 100) to prevent slow page loads | |
| 	// Users can view more tasks via pagination if needed | |
| 	allTasks := manager.GetTasks("", "", 100) | |
| 	return allTasks, nil | |
| } | |
| 
 | |
| func (h *MaintenanceHandlers) getMaintenanceWorkers() ([]*maintenance.MaintenanceWorker, error) { | |
| 	// Get workers from the admin server's maintenance manager | |
| 	if h.adminServer == nil { | |
| 		return []*maintenance.MaintenanceWorker{}, nil | |
| 	} | |
| 
 | |
| 	if h.adminServer.GetMaintenanceManager() == nil { | |
| 		return []*maintenance.MaintenanceWorker{}, nil | |
| 	} | |
| 
 | |
| 	// Get workers from the maintenance manager | |
| 	workers := h.adminServer.GetMaintenanceManager().GetWorkers() | |
| 	return workers, nil | |
| } | |
| 
 | |
| func (h *MaintenanceHandlers) getMaintenanceConfig() (*maintenance.MaintenanceConfigData, error) { | |
| 	// Delegate to AdminServer's real persistence method | |
| 	return h.adminServer.GetMaintenanceConfigData() | |
| } | |
| 
 | |
| func (h *MaintenanceHandlers) updateMaintenanceConfig(config *maintenance.MaintenanceConfig) error { | |
| 	// Delegate to AdminServer's real persistence method | |
| 	return h.adminServer.UpdateMaintenanceConfigData(config) | |
| } | |
| 
 | |
| // saveTaskConfigToProtobuf saves task configuration to protobuf file | |
| func (h *MaintenanceHandlers) saveTaskConfigToProtobuf(taskType types.TaskType, config TaskConfig) error { | |
| 	configPersistence := h.adminServer.GetConfigPersistence() | |
| 	if configPersistence == nil { | |
| 		return fmt.Errorf("config persistence not available") | |
| 	} | |
| 
 | |
| 	// Use the new ToTaskPolicy method - much simpler and more maintainable! | |
| 	taskPolicy := config.ToTaskPolicy() | |
| 
 | |
| 	// Save using task-specific methods | |
| 	switch taskType { | |
| 	case types.TaskTypeVacuum: | |
| 		return configPersistence.SaveVacuumTaskPolicy(taskPolicy) | |
| 	case types.TaskTypeErasureCoding: | |
| 		return configPersistence.SaveErasureCodingTaskPolicy(taskPolicy) | |
| 	case types.TaskTypeBalance: | |
| 		return configPersistence.SaveBalanceTaskPolicy(taskPolicy) | |
| 	default: | |
| 		return fmt.Errorf("unsupported task type for protobuf persistence: %s", taskType) | |
| 	} | |
| }
 |