diff options
Diffstat (limited to 'weed/worker/tasks')
| -rw-r--r-- | weed/worker/tasks/balance/ui_templ.go | 369 | ||||
| -rw-r--r-- | weed/worker/tasks/erasure_coding/ui_templ.go | 319 | ||||
| -rw-r--r-- | weed/worker/tasks/vacuum/ui_templ.go | 330 |
3 files changed, 0 insertions, 1018 deletions
diff --git a/weed/worker/tasks/balance/ui_templ.go b/weed/worker/tasks/balance/ui_templ.go deleted file mode 100644 index 54998af4c..000000000 --- a/weed/worker/tasks/balance/ui_templ.go +++ /dev/null @@ -1,369 +0,0 @@ -package balance - -import ( - "fmt" - "strconv" - "time" - - "github.com/seaweedfs/seaweedfs/weed/admin/view/components" - "github.com/seaweedfs/seaweedfs/weed/glog" - "github.com/seaweedfs/seaweedfs/weed/worker/types" -) - -// Helper function to format seconds as duration string -func formatDurationFromSeconds(seconds int) string { - d := time.Duration(seconds) * time.Second - return d.String() -} - -// Helper functions to convert between seconds and value+unit format -func secondsToValueAndUnit(seconds int) (float64, string) { - if seconds == 0 { - return 0, "minutes" - } - - // Try days first - if seconds%(24*3600) == 0 && seconds >= 24*3600 { - return float64(seconds / (24 * 3600)), "days" - } - - // Try hours - if seconds%3600 == 0 && seconds >= 3600 { - return float64(seconds / 3600), "hours" - } - - // Default to minutes - return float64(seconds / 60), "minutes" -} - -func valueAndUnitToSeconds(value float64, unit string) int { - switch unit { - case "days": - return int(value * 24 * 3600) - case "hours": - return int(value * 3600) - case "minutes": - return int(value * 60) - default: - return int(value * 60) // Default to minutes - } -} - -// UITemplProvider provides the templ-based UI for balance task configuration -type UITemplProvider struct { - detector *BalanceDetector - scheduler *BalanceScheduler -} - -// NewUITemplProvider creates a new balance templ UI provider -func NewUITemplProvider(detector *BalanceDetector, scheduler *BalanceScheduler) *UITemplProvider { - return &UITemplProvider{ - detector: detector, - scheduler: scheduler, - } -} - -// GetTaskType returns the task type -func (ui *UITemplProvider) GetTaskType() types.TaskType { - return types.TaskTypeBalance -} - -// GetDisplayName returns the human-readable name -func (ui *UITemplProvider) GetDisplayName() string { - return "Volume Balance" -} - -// GetDescription returns a description of what this task does -func (ui *UITemplProvider) GetDescription() string { - return "Redistributes volumes across volume servers to optimize storage utilization and performance" -} - -// GetIcon returns the icon CSS class for this task type -func (ui *UITemplProvider) GetIcon() string { - return "fas fa-balance-scale text-secondary" -} - -// RenderConfigSections renders the configuration as templ section data -func (ui *UITemplProvider) RenderConfigSections(currentConfig interface{}) ([]components.ConfigSectionData, error) { - config := ui.getCurrentBalanceConfig() - - // Detection settings section - detectionSection := components.ConfigSectionData{ - Title: "Detection Settings", - Icon: "fas fa-search", - Description: "Configure when balance tasks should be triggered", - Fields: []interface{}{ - components.CheckboxFieldData{ - FormFieldData: components.FormFieldData{ - Name: "enabled", - Label: "Enable Balance Tasks", - Description: "Whether balance tasks should be automatically created", - }, - Checked: config.Enabled, - }, - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "imbalance_threshold", - Label: "Imbalance Threshold", - Description: "Trigger balance when storage imbalance exceeds this percentage (0.0-1.0)", - Required: true, - }, - Value: config.ImbalanceThreshold, - Step: "0.01", - Min: floatPtr(0.0), - Max: floatPtr(1.0), - }, - components.DurationInputFieldData{ - FormFieldData: components.FormFieldData{ - Name: "scan_interval", - Label: "Scan Interval", - Description: "How often to scan for imbalanced volumes", - Required: true, - }, - Seconds: config.ScanIntervalSeconds, - }, - }, - } - - // Scheduling settings section - schedulingSection := components.ConfigSectionData{ - Title: "Scheduling Settings", - Icon: "fas fa-clock", - Description: "Configure task scheduling and concurrency", - Fields: []interface{}{ - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "max_concurrent", - Label: "Max Concurrent Tasks", - Description: "Maximum number of balance tasks that can run simultaneously", - Required: true, - }, - Value: float64(config.MaxConcurrent), - Step: "1", - Min: floatPtr(1), - }, - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "min_server_count", - Label: "Minimum Server Count", - Description: "Only balance when at least this many servers are available", - Required: true, - }, - Value: float64(config.MinServerCount), - Step: "1", - Min: floatPtr(1), - }, - }, - } - - // Timing constraints section - timingSection := components.ConfigSectionData{ - Title: "Timing Constraints", - Icon: "fas fa-calendar-clock", - Description: "Configure when balance operations are allowed", - Fields: []interface{}{ - components.CheckboxFieldData{ - FormFieldData: components.FormFieldData{ - Name: "move_during_off_hours", - Label: "Restrict to Off-Hours", - Description: "Only perform balance operations during off-peak hours", - }, - Checked: config.MoveDuringOffHours, - }, - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "off_hours_start", - Label: "Off-Hours Start Time", - Description: "Start time for off-hours window (e.g., 23:00)", - }, - Value: config.OffHoursStart, - }, - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "off_hours_end", - Label: "Off-Hours End Time", - Description: "End time for off-hours window (e.g., 06:00)", - }, - Value: config.OffHoursEnd, - }, - }, - } - - // Performance impact info section - performanceSection := components.ConfigSectionData{ - Title: "Performance Considerations", - Icon: "fas fa-exclamation-triangle", - Description: "Important information about balance operations", - Fields: []interface{}{ - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "performance_info", - Label: "Performance Impact", - Description: "Volume balancing involves data movement and can impact cluster performance", - }, - Value: "Enable off-hours restriction to minimize impact on production workloads", - }, - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "safety_info", - Label: "Safety Requirements", - Description: fmt.Sprintf("Requires at least %d servers to ensure data safety during moves", config.MinServerCount), - }, - Value: "Maintains data safety during volume moves between servers", - }, - }, - } - - return []components.ConfigSectionData{detectionSection, schedulingSection, timingSection, performanceSection}, nil -} - -// ParseConfigForm parses form data into configuration -func (ui *UITemplProvider) ParseConfigForm(formData map[string][]string) (interface{}, error) { - config := &BalanceConfig{} - - // Parse enabled checkbox - config.Enabled = len(formData["enabled"]) > 0 && formData["enabled"][0] == "on" - - // Parse imbalance threshold - if thresholdStr := formData["imbalance_threshold"]; len(thresholdStr) > 0 { - if threshold, err := strconv.ParseFloat(thresholdStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid imbalance threshold: %v", err) - } else if threshold < 0 || threshold > 1 { - return nil, fmt.Errorf("imbalance threshold must be between 0.0 and 1.0") - } else { - config.ImbalanceThreshold = threshold - } - } - - // Parse scan interval - if valueStr := formData["scan_interval"]; len(valueStr) > 0 { - if value, err := strconv.ParseFloat(valueStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid scan interval value: %v", err) - } else { - unit := "minutes" // default - if unitStr := formData["scan_interval_unit"]; len(unitStr) > 0 { - unit = unitStr[0] - } - config.ScanIntervalSeconds = valueAndUnitToSeconds(value, unit) - } - } - - // Parse max concurrent - if concurrentStr := formData["max_concurrent"]; len(concurrentStr) > 0 { - if concurrent, err := strconv.Atoi(concurrentStr[0]); err != nil { - return nil, fmt.Errorf("invalid max concurrent: %v", err) - } else if concurrent < 1 { - return nil, fmt.Errorf("max concurrent must be at least 1") - } else { - config.MaxConcurrent = concurrent - } - } - - // Parse min server count - if serverCountStr := formData["min_server_count"]; len(serverCountStr) > 0 { - if serverCount, err := strconv.Atoi(serverCountStr[0]); err != nil { - return nil, fmt.Errorf("invalid min server count: %v", err) - } else if serverCount < 1 { - return nil, fmt.Errorf("min server count must be at least 1") - } else { - config.MinServerCount = serverCount - } - } - - // Parse move during off hours - config.MoveDuringOffHours = len(formData["move_during_off_hours"]) > 0 && formData["move_during_off_hours"][0] == "on" - - // Parse off hours start time - if startStr := formData["off_hours_start"]; len(startStr) > 0 { - config.OffHoursStart = startStr[0] - } - - // Parse off hours end time - if endStr := formData["off_hours_end"]; len(endStr) > 0 { - config.OffHoursEnd = endStr[0] - } - - return config, nil -} - -// GetCurrentConfig returns the current configuration -func (ui *UITemplProvider) GetCurrentConfig() interface{} { - return ui.getCurrentBalanceConfig() -} - -// ApplyConfig applies the new configuration -func (ui *UITemplProvider) ApplyConfig(config interface{}) error { - balanceConfig, ok := config.(*BalanceConfig) - if !ok { - return fmt.Errorf("invalid config type, expected *BalanceConfig") - } - - // Apply to detector - if ui.detector != nil { - ui.detector.SetEnabled(balanceConfig.Enabled) - ui.detector.SetThreshold(balanceConfig.ImbalanceThreshold) - ui.detector.SetMinCheckInterval(time.Duration(balanceConfig.ScanIntervalSeconds) * time.Second) - } - - // Apply to scheduler - if ui.scheduler != nil { - ui.scheduler.SetEnabled(balanceConfig.Enabled) - ui.scheduler.SetMaxConcurrent(balanceConfig.MaxConcurrent) - ui.scheduler.SetMinServerCount(balanceConfig.MinServerCount) - ui.scheduler.SetMoveDuringOffHours(balanceConfig.MoveDuringOffHours) - ui.scheduler.SetOffHoursStart(balanceConfig.OffHoursStart) - ui.scheduler.SetOffHoursEnd(balanceConfig.OffHoursEnd) - } - - glog.V(1).Infof("Applied balance configuration: enabled=%v, threshold=%.1f%%, max_concurrent=%d, min_servers=%d, off_hours=%v", - balanceConfig.Enabled, balanceConfig.ImbalanceThreshold*100, balanceConfig.MaxConcurrent, - balanceConfig.MinServerCount, balanceConfig.MoveDuringOffHours) - - return nil -} - -// getCurrentBalanceConfig gets the current configuration from detector and scheduler -func (ui *UITemplProvider) getCurrentBalanceConfig() *BalanceConfig { - config := &BalanceConfig{ - // Default values (fallback if detectors/schedulers are nil) - Enabled: true, - ImbalanceThreshold: 0.1, // 10% imbalance - ScanIntervalSeconds: int((4 * time.Hour).Seconds()), - MaxConcurrent: 1, - MinServerCount: 3, - MoveDuringOffHours: true, - OffHoursStart: "23:00", - OffHoursEnd: "06:00", - } - - // Get current values from detector - if ui.detector != nil { - config.Enabled = ui.detector.IsEnabled() - config.ImbalanceThreshold = ui.detector.GetThreshold() - config.ScanIntervalSeconds = int(ui.detector.ScanInterval().Seconds()) - } - - // Get current values from scheduler - if ui.scheduler != nil { - config.MaxConcurrent = ui.scheduler.GetMaxConcurrent() - config.MinServerCount = ui.scheduler.GetMinServerCount() - config.MoveDuringOffHours = ui.scheduler.GetMoveDuringOffHours() - config.OffHoursStart = ui.scheduler.GetOffHoursStart() - config.OffHoursEnd = ui.scheduler.GetOffHoursEnd() - } - - return config -} - -// floatPtr is a helper function to create float64 pointers -func floatPtr(f float64) *float64 { - return &f -} - -// RegisterUITempl registers the balance templ UI provider with the UI registry -func RegisterUITempl(uiRegistry *types.UITemplRegistry, detector *BalanceDetector, scheduler *BalanceScheduler) { - uiProvider := NewUITemplProvider(detector, scheduler) - uiRegistry.RegisterUI(uiProvider) - - glog.V(1).Infof("✅ Registered balance task templ UI provider") -} diff --git a/weed/worker/tasks/erasure_coding/ui_templ.go b/weed/worker/tasks/erasure_coding/ui_templ.go deleted file mode 100644 index 12c3d199e..000000000 --- a/weed/worker/tasks/erasure_coding/ui_templ.go +++ /dev/null @@ -1,319 +0,0 @@ -package erasure_coding - -import ( - "fmt" - "strconv" - "time" - - "github.com/seaweedfs/seaweedfs/weed/admin/view/components" - "github.com/seaweedfs/seaweedfs/weed/glog" - "github.com/seaweedfs/seaweedfs/weed/worker/types" -) - -// Helper function to format seconds as duration string -func formatDurationFromSeconds(seconds int) string { - d := time.Duration(seconds) * time.Second - return d.String() -} - -// Helper function to convert value and unit to seconds -func valueAndUnitToSeconds(value float64, unit string) int { - switch unit { - case "days": - return int(value * 24 * 60 * 60) - case "hours": - return int(value * 60 * 60) - case "minutes": - return int(value * 60) - default: - return int(value * 60) // Default to minutes - } -} - -// UITemplProvider provides the templ-based UI for erasure coding task configuration -type UITemplProvider struct { - detector *EcDetector - scheduler *Scheduler -} - -// NewUITemplProvider creates a new erasure coding templ UI provider -func NewUITemplProvider(detector *EcDetector, scheduler *Scheduler) *UITemplProvider { - return &UITemplProvider{ - detector: detector, - scheduler: scheduler, - } -} - -// ErasureCodingConfig is defined in ui.go - we reuse it - -// GetTaskType returns the task type -func (ui *UITemplProvider) GetTaskType() types.TaskType { - return types.TaskTypeErasureCoding -} - -// GetDisplayName returns the human-readable name -func (ui *UITemplProvider) GetDisplayName() string { - return "Erasure Coding" -} - -// GetDescription returns a description of what this task does -func (ui *UITemplProvider) GetDescription() string { - return "Converts replicated volumes to erasure-coded format for efficient storage" -} - -// GetIcon returns the icon CSS class for this task type -func (ui *UITemplProvider) GetIcon() string { - return "fas fa-shield-alt text-info" -} - -// RenderConfigSections renders the configuration as templ section data -func (ui *UITemplProvider) RenderConfigSections(currentConfig interface{}) ([]components.ConfigSectionData, error) { - config := ui.getCurrentECConfig() - - // Detection settings section - detectionSection := components.ConfigSectionData{ - Title: "Detection Settings", - Icon: "fas fa-search", - Description: "Configure when erasure coding tasks should be triggered", - Fields: []interface{}{ - components.CheckboxFieldData{ - FormFieldData: components.FormFieldData{ - Name: "enabled", - Label: "Enable Erasure Coding Tasks", - Description: "Whether erasure coding tasks should be automatically created", - }, - Checked: config.Enabled, - }, - components.DurationInputFieldData{ - FormFieldData: components.FormFieldData{ - Name: "scan_interval", - Label: "Scan Interval", - Description: "How often to scan for volumes needing erasure coding", - Required: true, - }, - Seconds: config.ScanIntervalSeconds, - }, - components.DurationInputFieldData{ - FormFieldData: components.FormFieldData{ - Name: "volume_age_threshold", - Label: "Volume Age Threshold", - Description: "Only apply erasure coding to volumes older than this age", - Required: true, - }, - Seconds: config.VolumeAgeHoursSeconds, - }, - }, - } - - // Erasure coding parameters section - paramsSection := components.ConfigSectionData{ - Title: "Erasure Coding Parameters", - Icon: "fas fa-cogs", - Description: "Configure erasure coding scheme and performance", - Fields: []interface{}{ - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "data_shards", - Label: "Data Shards", - Description: "Number of data shards in the erasure coding scheme", - Required: true, - }, - Value: float64(config.ShardCount), - Step: "1", - Min: floatPtr(1), - Max: floatPtr(16), - }, - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "parity_shards", - Label: "Parity Shards", - Description: "Number of parity shards (determines fault tolerance)", - Required: true, - }, - Value: float64(config.ParityCount), - Step: "1", - Min: floatPtr(1), - Max: floatPtr(16), - }, - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "max_concurrent", - Label: "Max Concurrent Tasks", - Description: "Maximum number of erasure coding tasks that can run simultaneously", - Required: true, - }, - Value: float64(config.MaxConcurrent), - Step: "1", - Min: floatPtr(1), - }, - }, - } - - // Performance impact info section - infoSection := components.ConfigSectionData{ - Title: "Performance Impact", - Icon: "fas fa-info-circle", - Description: "Important information about erasure coding operations", - Fields: []interface{}{ - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "durability_info", - Label: "Durability", - Description: fmt.Sprintf("With %d+%d configuration, can tolerate up to %d shard failures", - config.ShardCount, config.ParityCount, config.ParityCount), - }, - Value: "High durability with space efficiency", - }, - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "performance_info", - Label: "Performance Note", - Description: "Erasure coding is CPU and I/O intensive. Consider running during off-peak hours", - }, - Value: "Schedule during low-traffic periods", - }, - }, - } - - return []components.ConfigSectionData{detectionSection, paramsSection, infoSection}, nil -} - -// ParseConfigForm parses form data into configuration -func (ui *UITemplProvider) ParseConfigForm(formData map[string][]string) (interface{}, error) { - config := &ErasureCodingConfig{} - - // Parse enabled checkbox - config.Enabled = len(formData["enabled"]) > 0 && formData["enabled"][0] == "on" - - // Parse volume age threshold - if valueStr := formData["volume_age_threshold"]; len(valueStr) > 0 { - if value, err := strconv.ParseFloat(valueStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid volume age threshold value: %v", err) - } else { - unit := "hours" // default - if unitStr := formData["volume_age_threshold_unit"]; len(unitStr) > 0 { - unit = unitStr[0] - } - config.VolumeAgeHoursSeconds = valueAndUnitToSeconds(value, unit) - } - } - - // Parse scan interval - if valueStr := formData["scan_interval"]; len(valueStr) > 0 { - if value, err := strconv.ParseFloat(valueStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid scan interval value: %v", err) - } else { - unit := "hours" // default - if unitStr := formData["scan_interval_unit"]; len(unitStr) > 0 { - unit = unitStr[0] - } - config.ScanIntervalSeconds = valueAndUnitToSeconds(value, unit) - } - } - - // Parse data shards - if shardsStr := formData["data_shards"]; len(shardsStr) > 0 { - if shards, err := strconv.Atoi(shardsStr[0]); err != nil { - return nil, fmt.Errorf("invalid data shards: %v", err) - } else if shards < 1 || shards > 16 { - return nil, fmt.Errorf("data shards must be between 1 and 16") - } else { - config.ShardCount = shards - } - } - - // Parse parity shards - if shardsStr := formData["parity_shards"]; len(shardsStr) > 0 { - if shards, err := strconv.Atoi(shardsStr[0]); err != nil { - return nil, fmt.Errorf("invalid parity shards: %v", err) - } else if shards < 1 || shards > 16 { - return nil, fmt.Errorf("parity shards must be between 1 and 16") - } else { - config.ParityCount = shards - } - } - - // Parse max concurrent - if concurrentStr := formData["max_concurrent"]; len(concurrentStr) > 0 { - if concurrent, err := strconv.Atoi(concurrentStr[0]); err != nil { - return nil, fmt.Errorf("invalid max concurrent: %v", err) - } else if concurrent < 1 { - return nil, fmt.Errorf("max concurrent must be at least 1") - } else { - config.MaxConcurrent = concurrent - } - } - - return config, nil -} - -// GetCurrentConfig returns the current configuration -func (ui *UITemplProvider) GetCurrentConfig() interface{} { - return ui.getCurrentECConfig() -} - -// ApplyConfig applies the new configuration -func (ui *UITemplProvider) ApplyConfig(config interface{}) error { - ecConfig, ok := config.(*ErasureCodingConfig) - if !ok { - return fmt.Errorf("invalid config type, expected *ErasureCodingConfig") - } - - // Apply to detector - if ui.detector != nil { - ui.detector.SetEnabled(ecConfig.Enabled) - ui.detector.SetVolumeAgeHours(ecConfig.VolumeAgeHoursSeconds) - ui.detector.SetScanInterval(time.Duration(ecConfig.ScanIntervalSeconds) * time.Second) - } - - // Apply to scheduler - if ui.scheduler != nil { - ui.scheduler.SetMaxConcurrent(ecConfig.MaxConcurrent) - ui.scheduler.SetEnabled(ecConfig.Enabled) - } - - glog.V(1).Infof("Applied erasure coding configuration: enabled=%v, age_threshold=%ds, max_concurrent=%d", - ecConfig.Enabled, ecConfig.VolumeAgeHoursSeconds, ecConfig.MaxConcurrent) - - return nil -} - -// getCurrentECConfig gets the current configuration from detector and scheduler -func (ui *UITemplProvider) getCurrentECConfig() *ErasureCodingConfig { - config := &ErasureCodingConfig{ - // Default values (fallback if detectors/schedulers are nil) - Enabled: true, - VolumeAgeHoursSeconds: int((24 * time.Hour).Seconds()), - ScanIntervalSeconds: int((2 * time.Hour).Seconds()), - MaxConcurrent: 1, - ShardCount: 10, - ParityCount: 4, - } - - // Get current values from detector - if ui.detector != nil { - config.Enabled = ui.detector.IsEnabled() - config.VolumeAgeHoursSeconds = ui.detector.GetVolumeAgeHours() - config.ScanIntervalSeconds = int(ui.detector.ScanInterval().Seconds()) - } - - // Get current values from scheduler - if ui.scheduler != nil { - config.MaxConcurrent = ui.scheduler.GetMaxConcurrent() - } - - return config -} - -// floatPtr is a helper function to create float64 pointers -func floatPtr(f float64) *float64 { - return &f -} - -// RegisterUITempl registers the erasure coding templ UI provider with the UI registry -func RegisterUITempl(uiRegistry *types.UITemplRegistry, detector *EcDetector, scheduler *Scheduler) { - uiProvider := NewUITemplProvider(detector, scheduler) - uiRegistry.RegisterUI(uiProvider) - - glog.V(1).Infof("✅ Registered erasure coding task templ UI provider") -} diff --git a/weed/worker/tasks/vacuum/ui_templ.go b/weed/worker/tasks/vacuum/ui_templ.go deleted file mode 100644 index 15558b832..000000000 --- a/weed/worker/tasks/vacuum/ui_templ.go +++ /dev/null @@ -1,330 +0,0 @@ -package vacuum - -import ( - "fmt" - "strconv" - "time" - - "github.com/seaweedfs/seaweedfs/weed/admin/view/components" - "github.com/seaweedfs/seaweedfs/weed/glog" - "github.com/seaweedfs/seaweedfs/weed/worker/types" -) - -// Helper function to format seconds as duration string -func formatDurationFromSeconds(seconds int) string { - d := time.Duration(seconds) * time.Second - return d.String() -} - -// Helper functions to convert between seconds and value+unit format -func secondsToValueAndUnit(seconds int) (float64, string) { - if seconds == 0 { - return 0, "minutes" - } - - // Try days first - if seconds%(24*3600) == 0 && seconds >= 24*3600 { - return float64(seconds / (24 * 3600)), "days" - } - - // Try hours - if seconds%3600 == 0 && seconds >= 3600 { - return float64(seconds / 3600), "hours" - } - - // Default to minutes - return float64(seconds / 60), "minutes" -} - -func valueAndUnitToSeconds(value float64, unit string) int { - switch unit { - case "days": - return int(value * 24 * 3600) - case "hours": - return int(value * 3600) - case "minutes": - return int(value * 60) - default: - return int(value * 60) // Default to minutes - } -} - -// UITemplProvider provides the templ-based UI for vacuum task configuration -type UITemplProvider struct { - detector *VacuumDetector - scheduler *VacuumScheduler -} - -// NewUITemplProvider creates a new vacuum templ UI provider -func NewUITemplProvider(detector *VacuumDetector, scheduler *VacuumScheduler) *UITemplProvider { - return &UITemplProvider{ - detector: detector, - scheduler: scheduler, - } -} - -// GetTaskType returns the task type -func (ui *UITemplProvider) GetTaskType() types.TaskType { - return types.TaskTypeVacuum -} - -// GetDisplayName returns the human-readable name -func (ui *UITemplProvider) GetDisplayName() string { - return "Volume Vacuum" -} - -// GetDescription returns a description of what this task does -func (ui *UITemplProvider) GetDescription() string { - return "Reclaims disk space by removing deleted files from volumes" -} - -// GetIcon returns the icon CSS class for this task type -func (ui *UITemplProvider) GetIcon() string { - return "fas fa-broom text-primary" -} - -// RenderConfigSections renders the configuration as templ section data -func (ui *UITemplProvider) RenderConfigSections(currentConfig interface{}) ([]components.ConfigSectionData, error) { - config := ui.getCurrentVacuumConfig() - - // Detection settings section - detectionSection := components.ConfigSectionData{ - Title: "Detection Settings", - Icon: "fas fa-search", - Description: "Configure when vacuum tasks should be triggered", - Fields: []interface{}{ - components.CheckboxFieldData{ - FormFieldData: components.FormFieldData{ - Name: "enabled", - Label: "Enable Vacuum Tasks", - Description: "Whether vacuum tasks should be automatically created", - }, - Checked: config.Enabled, - }, - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "garbage_threshold", - Label: "Garbage Threshold", - Description: "Trigger vacuum when garbage ratio exceeds this percentage (0.0-1.0)", - Required: true, - }, - Value: config.GarbageThreshold, - Step: "0.01", - Min: floatPtr(0.0), - Max: floatPtr(1.0), - }, - components.DurationInputFieldData{ - FormFieldData: components.FormFieldData{ - Name: "scan_interval", - Label: "Scan Interval", - Description: "How often to scan for volumes needing vacuum", - Required: true, - }, - Seconds: config.ScanIntervalSeconds, - }, - components.DurationInputFieldData{ - FormFieldData: components.FormFieldData{ - Name: "min_volume_age", - Label: "Minimum Volume Age", - Description: "Only vacuum volumes older than this duration", - Required: true, - }, - Seconds: config.MinVolumeAgeSeconds, - }, - }, - } - - // Scheduling settings section - schedulingSection := components.ConfigSectionData{ - Title: "Scheduling Settings", - Icon: "fas fa-clock", - Description: "Configure task scheduling and concurrency", - Fields: []interface{}{ - components.NumberFieldData{ - FormFieldData: components.FormFieldData{ - Name: "max_concurrent", - Label: "Max Concurrent Tasks", - Description: "Maximum number of vacuum tasks that can run simultaneously", - Required: true, - }, - Value: float64(config.MaxConcurrent), - Step: "1", - Min: floatPtr(1), - }, - components.DurationInputFieldData{ - FormFieldData: components.FormFieldData{ - Name: "min_interval", - Label: "Minimum Interval", - Description: "Minimum time between vacuum operations on the same volume", - Required: true, - }, - Seconds: config.MinIntervalSeconds, - }, - }, - } - - // Performance impact info section - performanceSection := components.ConfigSectionData{ - Title: "Performance Impact", - Icon: "fas fa-exclamation-triangle", - Description: "Important information about vacuum operations", - Fields: []interface{}{ - components.TextFieldData{ - FormFieldData: components.FormFieldData{ - Name: "info_impact", - Label: "Impact", - Description: "Volume vacuum operations are I/O intensive and should be scheduled appropriately", - }, - Value: "Configure thresholds and intervals based on your storage usage patterns", - }, - }, - } - - return []components.ConfigSectionData{detectionSection, schedulingSection, performanceSection}, nil -} - -// ParseConfigForm parses form data into configuration -func (ui *UITemplProvider) ParseConfigForm(formData map[string][]string) (interface{}, error) { - config := &VacuumConfig{} - - // Parse enabled checkbox - config.Enabled = len(formData["enabled"]) > 0 && formData["enabled"][0] == "on" - - // Parse garbage threshold - if thresholdStr := formData["garbage_threshold"]; len(thresholdStr) > 0 { - if threshold, err := strconv.ParseFloat(thresholdStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid garbage threshold: %v", err) - } else if threshold < 0 || threshold > 1 { - return nil, fmt.Errorf("garbage threshold must be between 0.0 and 1.0") - } else { - config.GarbageThreshold = threshold - } - } - - // Parse scan interval - if valueStr := formData["scan_interval"]; len(valueStr) > 0 { - if value, err := strconv.ParseFloat(valueStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid scan interval value: %v", err) - } else { - unit := "minutes" // default - if unitStr := formData["scan_interval_unit"]; len(unitStr) > 0 { - unit = unitStr[0] - } - config.ScanIntervalSeconds = valueAndUnitToSeconds(value, unit) - } - } - - // Parse min volume age - if valueStr := formData["min_volume_age"]; len(valueStr) > 0 { - if value, err := strconv.ParseFloat(valueStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid min volume age value: %v", err) - } else { - unit := "minutes" // default - if unitStr := formData["min_volume_age_unit"]; len(unitStr) > 0 { - unit = unitStr[0] - } - config.MinVolumeAgeSeconds = valueAndUnitToSeconds(value, unit) - } - } - - // Parse max concurrent - if concurrentStr := formData["max_concurrent"]; len(concurrentStr) > 0 { - if concurrent, err := strconv.Atoi(concurrentStr[0]); err != nil { - return nil, fmt.Errorf("invalid max concurrent: %v", err) - } else if concurrent < 1 { - return nil, fmt.Errorf("max concurrent must be at least 1") - } else { - config.MaxConcurrent = concurrent - } - } - - // Parse min interval - if valueStr := formData["min_interval"]; len(valueStr) > 0 { - if value, err := strconv.ParseFloat(valueStr[0], 64); err != nil { - return nil, fmt.Errorf("invalid min interval value: %v", err) - } else { - unit := "minutes" // default - if unitStr := formData["min_interval_unit"]; len(unitStr) > 0 { - unit = unitStr[0] - } - config.MinIntervalSeconds = valueAndUnitToSeconds(value, unit) - } - } - - return config, nil -} - -// GetCurrentConfig returns the current configuration -func (ui *UITemplProvider) GetCurrentConfig() interface{} { - return ui.getCurrentVacuumConfig() -} - -// ApplyConfig applies the new configuration -func (ui *UITemplProvider) ApplyConfig(config interface{}) error { - vacuumConfig, ok := config.(*VacuumConfig) - if !ok { - return fmt.Errorf("invalid config type, expected *VacuumConfig") - } - - // Apply to detector - if ui.detector != nil { - ui.detector.SetEnabled(vacuumConfig.Enabled) - ui.detector.SetGarbageThreshold(vacuumConfig.GarbageThreshold) - ui.detector.SetScanInterval(time.Duration(vacuumConfig.ScanIntervalSeconds) * time.Second) - ui.detector.SetMinVolumeAge(time.Duration(vacuumConfig.MinVolumeAgeSeconds) * time.Second) - } - - // Apply to scheduler - if ui.scheduler != nil { - ui.scheduler.SetEnabled(vacuumConfig.Enabled) - ui.scheduler.SetMaxConcurrent(vacuumConfig.MaxConcurrent) - ui.scheduler.SetMinInterval(time.Duration(vacuumConfig.MinIntervalSeconds) * time.Second) - } - - glog.V(1).Infof("Applied vacuum configuration: enabled=%v, threshold=%.1f%%, scan_interval=%s, max_concurrent=%d", - vacuumConfig.Enabled, vacuumConfig.GarbageThreshold*100, formatDurationFromSeconds(vacuumConfig.ScanIntervalSeconds), vacuumConfig.MaxConcurrent) - - return nil -} - -// getCurrentVacuumConfig gets the current configuration from detector and scheduler -func (ui *UITemplProvider) getCurrentVacuumConfig() *VacuumConfig { - config := &VacuumConfig{ - // Default values (fallback if detectors/schedulers are nil) - Enabled: true, - GarbageThreshold: 0.3, - ScanIntervalSeconds: int((30 * time.Minute).Seconds()), - MinVolumeAgeSeconds: int((1 * time.Hour).Seconds()), - MaxConcurrent: 2, - MinIntervalSeconds: int((6 * time.Hour).Seconds()), - } - - // Get current values from detector - if ui.detector != nil { - config.Enabled = ui.detector.IsEnabled() - config.GarbageThreshold = ui.detector.GetGarbageThreshold() - config.ScanIntervalSeconds = int(ui.detector.ScanInterval().Seconds()) - config.MinVolumeAgeSeconds = int(ui.detector.GetMinVolumeAge().Seconds()) - } - - // Get current values from scheduler - if ui.scheduler != nil { - config.MaxConcurrent = ui.scheduler.GetMaxConcurrent() - config.MinIntervalSeconds = int(ui.scheduler.GetMinInterval().Seconds()) - } - - return config -} - -// floatPtr is a helper function to create float64 pointers -func floatPtr(f float64) *float64 { - return &f -} - -// RegisterUITempl registers the vacuum templ UI provider with the UI registry -func RegisterUITempl(uiRegistry *types.UITemplRegistry, detector *VacuumDetector, scheduler *VacuumScheduler) { - uiProvider := NewUITemplProvider(detector, scheduler) - uiRegistry.RegisterUI(uiProvider) - - glog.V(1).Infof("✅ Registered vacuum task templ UI provider") -} |
