diff options
Diffstat (limited to 'weed/worker/tasks/vacuum/ui.go')
| -rw-r--r-- | weed/worker/tasks/vacuum/ui.go | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/weed/worker/tasks/vacuum/ui.go b/weed/worker/tasks/vacuum/ui.go new file mode 100644 index 000000000..a315dde88 --- /dev/null +++ b/weed/worker/tasks/vacuum/ui.go @@ -0,0 +1,314 @@ +package vacuum + +import ( + "fmt" + "html/template" + "strconv" + "time" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/worker/types" +) + +// UIProvider provides the UI for vacuum task configuration +type UIProvider struct { + detector *VacuumDetector + scheduler *VacuumScheduler +} + +// NewUIProvider creates a new vacuum UI provider +func NewUIProvider(detector *VacuumDetector, scheduler *VacuumScheduler) *UIProvider { + return &UIProvider{ + detector: detector, + scheduler: scheduler, + } +} + +// GetTaskType returns the task type +func (ui *UIProvider) GetTaskType() types.TaskType { + return types.TaskTypeVacuum +} + +// GetDisplayName returns the human-readable name +func (ui *UIProvider) GetDisplayName() string { + return "Volume Vacuum" +} + +// GetDescription returns a description of what this task does +func (ui *UIProvider) GetDescription() string { + return "Reclaims disk space by removing deleted files from volumes" +} + +// GetIcon returns the icon CSS class for this task type +func (ui *UIProvider) GetIcon() string { + return "fas fa-broom text-primary" +} + +// VacuumConfig represents the vacuum configuration +type VacuumConfig struct { + Enabled bool `json:"enabled"` + GarbageThreshold float64 `json:"garbage_threshold"` + ScanIntervalSeconds int `json:"scan_interval_seconds"` + MaxConcurrent int `json:"max_concurrent"` + MinVolumeAgeSeconds int `json:"min_volume_age_seconds"` + MinIntervalSeconds int `json:"min_interval_seconds"` +} + +// Helper functions for duration conversion +func secondsToDuration(seconds int) time.Duration { + return time.Duration(seconds) * time.Second +} + +func durationToSeconds(d time.Duration) int { + return int(d.Seconds()) +} + +// formatDurationForUser formats seconds as a user-friendly duration string +func formatDurationForUser(seconds int) string { + d := secondsToDuration(seconds) + if d < time.Minute { + return fmt.Sprintf("%ds", seconds) + } + if d < time.Hour { + return fmt.Sprintf("%.0fm", d.Minutes()) + } + if d < 24*time.Hour { + return fmt.Sprintf("%.1fh", d.Hours()) + } + return fmt.Sprintf("%.1fd", d.Hours()/24) +} + +// RenderConfigForm renders the configuration form HTML +func (ui *UIProvider) RenderConfigForm(currentConfig interface{}) (template.HTML, error) { + config := ui.getCurrentVacuumConfig() + + // Build form using the FormBuilder helper + form := types.NewFormBuilder() + + // Detection Settings + form.AddCheckboxField( + "enabled", + "Enable Vacuum Tasks", + "Whether vacuum tasks should be automatically created", + config.Enabled, + ) + + form.AddNumberField( + "garbage_threshold", + "Garbage Threshold (%)", + "Trigger vacuum when garbage ratio exceeds this percentage (0.0-1.0)", + config.GarbageThreshold, + true, + ) + + form.AddDurationField( + "scan_interval", + "Scan Interval", + "How often to scan for volumes needing vacuum", + secondsToDuration(config.ScanIntervalSeconds), + true, + ) + + form.AddDurationField( + "min_volume_age", + "Minimum Volume Age", + "Only vacuum volumes older than this duration", + secondsToDuration(config.MinVolumeAgeSeconds), + true, + ) + + // Scheduling Settings + form.AddNumberField( + "max_concurrent", + "Max Concurrent Tasks", + "Maximum number of vacuum tasks that can run simultaneously", + float64(config.MaxConcurrent), + true, + ) + + form.AddDurationField( + "min_interval", + "Minimum Interval", + "Minimum time between vacuum operations on the same volume", + secondsToDuration(config.MinIntervalSeconds), + true, + ) + + // Generate organized form sections using Bootstrap components + html := ` +<div class="row"> + <div class="col-12"> + <div class="card mb-4"> + <div class="card-header"> + <h5 class="mb-0"> + <i class="fas fa-search me-2"></i> + Detection Settings + </h5> + </div> + <div class="card-body"> +` + string(form.Build()) + ` + </div> + </div> + </div> +</div> + +<script> +function resetForm() { + if (confirm('Reset all vacuum settings to defaults?')) { + // Reset to default values + document.querySelector('input[name="enabled"]').checked = true; + document.querySelector('input[name="garbage_threshold"]').value = '0.3'; + document.querySelector('input[name="scan_interval"]').value = '30m'; + document.querySelector('input[name="min_volume_age"]').value = '1h'; + document.querySelector('input[name="max_concurrent"]').value = '2'; + document.querySelector('input[name="min_interval"]').value = '6h'; + } +} +</script> +` + + return template.HTML(html), nil +} + +// ParseConfigForm parses form data into configuration +func (ui *UIProvider) 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 intervalStr := formData["scan_interval"]; len(intervalStr) > 0 { + if interval, err := time.ParseDuration(intervalStr[0]); err != nil { + return nil, fmt.Errorf("invalid scan interval: %v", err) + } else { + config.ScanIntervalSeconds = durationToSeconds(interval) + } + } + + // Parse min volume age + if ageStr := formData["min_volume_age"]; len(ageStr) > 0 { + if age, err := time.ParseDuration(ageStr[0]); err != nil { + return nil, fmt.Errorf("invalid min volume age: %v", err) + } else { + config.MinVolumeAgeSeconds = durationToSeconds(age) + } + } + + // 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 intervalStr := formData["min_interval"]; len(intervalStr) > 0 { + if interval, err := time.ParseDuration(intervalStr[0]); err != nil { + return nil, fmt.Errorf("invalid min interval: %v", err) + } else { + config.MinIntervalSeconds = durationToSeconds(interval) + } + } + + return config, nil +} + +// GetCurrentConfig returns the current configuration +func (ui *UIProvider) GetCurrentConfig() interface{} { + return ui.getCurrentVacuumConfig() +} + +// ApplyConfig applies the new configuration +func (ui *UIProvider) 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(secondsToDuration(vacuumConfig.ScanIntervalSeconds)) + ui.detector.SetMinVolumeAge(secondsToDuration(vacuumConfig.MinVolumeAgeSeconds)) + } + + // Apply to scheduler + if ui.scheduler != nil { + ui.scheduler.SetEnabled(vacuumConfig.Enabled) + ui.scheduler.SetMaxConcurrent(vacuumConfig.MaxConcurrent) + ui.scheduler.SetMinInterval(secondsToDuration(vacuumConfig.MinIntervalSeconds)) + } + + glog.V(1).Infof("Applied vacuum configuration: enabled=%v, threshold=%.1f%%, scan_interval=%s, max_concurrent=%d", + vacuumConfig.Enabled, vacuumConfig.GarbageThreshold*100, formatDurationForUser(vacuumConfig.ScanIntervalSeconds), vacuumConfig.MaxConcurrent) + + return nil +} + +// getCurrentVacuumConfig gets the current configuration from detector and scheduler +func (ui *UIProvider) getCurrentVacuumConfig() *VacuumConfig { + config := &VacuumConfig{ + // Default values (fallback if detectors/schedulers are nil) + Enabled: true, + GarbageThreshold: 0.3, + ScanIntervalSeconds: 30 * 60, + MinVolumeAgeSeconds: 1 * 60 * 60, + MaxConcurrent: 2, + MinIntervalSeconds: 6 * 60 * 60, + } + + // Get current values from detector + if ui.detector != nil { + config.Enabled = ui.detector.IsEnabled() + config.GarbageThreshold = ui.detector.GetGarbageThreshold() + config.ScanIntervalSeconds = durationToSeconds(ui.detector.ScanInterval()) + config.MinVolumeAgeSeconds = durationToSeconds(ui.detector.GetMinVolumeAge()) + } + + // Get current values from scheduler + if ui.scheduler != nil { + config.MaxConcurrent = ui.scheduler.GetMaxConcurrent() + config.MinIntervalSeconds = durationToSeconds(ui.scheduler.GetMinInterval()) + } + + return config +} + +// RegisterUI registers the vacuum UI provider with the UI registry +func RegisterUI(uiRegistry *types.UIRegistry, detector *VacuumDetector, scheduler *VacuumScheduler) { + uiProvider := NewUIProvider(detector, scheduler) + uiRegistry.RegisterUI(uiProvider) + + glog.V(1).Infof("✅ Registered vacuum task UI provider") +} + +// Example: How to get the UI provider for external use +func GetUIProvider(uiRegistry *types.UIRegistry) *UIProvider { + provider := uiRegistry.GetProvider(types.TaskTypeVacuum) + if provider == nil { + return nil + } + + if vacuumProvider, ok := provider.(*UIProvider); ok { + return vacuumProvider + } + + return nil +} |
