9 changed files with 2503 additions and 931 deletions
-
350weed/admin/config/schema.go
-
39weed/admin/handlers/maintenance_handlers.go
-
444weed/admin/maintenance/config_schema.go
-
2weed/admin/maintenance/maintenance_types.go
-
264weed/admin/view/app/maintenance_config_schema.templ
-
554weed/admin/view/app/maintenance_config_schema_templ.go
-
396weed/admin/view/app/task_config_schema.templ
-
877weed/admin/view/app/task_config_schema_templ.go
-
310weed/worker/tasks/task_config_schema.go
@ -0,0 +1,350 @@ |
|||||
|
package config |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"reflect" |
||||
|
"strings" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// FieldType defines the type of a configuration field
|
||||
|
type FieldType string |
||||
|
|
||||
|
const ( |
||||
|
FieldTypeBool FieldType = "bool" |
||||
|
FieldTypeInt FieldType = "int" |
||||
|
FieldTypeDuration FieldType = "duration" |
||||
|
FieldTypeInterval FieldType = "interval" |
||||
|
FieldTypeString FieldType = "string" |
||||
|
FieldTypeFloat FieldType = "float" |
||||
|
) |
||||
|
|
||||
|
// FieldUnit defines the unit for display purposes
|
||||
|
type FieldUnit string |
||||
|
|
||||
|
const ( |
||||
|
UnitSeconds FieldUnit = "seconds" |
||||
|
UnitMinutes FieldUnit = "minutes" |
||||
|
UnitHours FieldUnit = "hours" |
||||
|
UnitDays FieldUnit = "days" |
||||
|
UnitCount FieldUnit = "count" |
||||
|
UnitNone FieldUnit = "" |
||||
|
) |
||||
|
|
||||
|
// Field defines a configuration field with all its metadata
|
||||
|
type Field struct { |
||||
|
// Field identification
|
||||
|
Name string `json:"name"` |
||||
|
JSONName string `json:"json_name"` |
||||
|
Type FieldType `json:"type"` |
||||
|
|
||||
|
// Default value and validation
|
||||
|
DefaultValue interface{} `json:"default_value"` |
||||
|
MinValue interface{} `json:"min_value,omitempty"` |
||||
|
MaxValue interface{} `json:"max_value,omitempty"` |
||||
|
Required bool `json:"required"` |
||||
|
|
||||
|
// UI display
|
||||
|
DisplayName string `json:"display_name"` |
||||
|
Description string `json:"description"` |
||||
|
HelpText string `json:"help_text"` |
||||
|
Placeholder string `json:"placeholder"` |
||||
|
Unit FieldUnit `json:"unit"` |
||||
|
|
||||
|
// Form rendering
|
||||
|
InputType string `json:"input_type"` // "checkbox", "number", "text", "interval", etc.
|
||||
|
CSSClasses string `json:"css_classes,omitempty"` |
||||
|
} |
||||
|
|
||||
|
// GetDisplayValue returns the value formatted for display in the specified unit
|
||||
|
func (f *Field) GetDisplayValue(value interface{}) interface{} { |
||||
|
if (f.Type == FieldTypeDuration || f.Type == FieldTypeInterval) && f.Unit != UnitSeconds { |
||||
|
if duration, ok := value.(time.Duration); ok { |
||||
|
switch f.Unit { |
||||
|
case UnitMinutes: |
||||
|
return int(duration.Minutes()) |
||||
|
case UnitHours: |
||||
|
return int(duration.Hours()) |
||||
|
case UnitDays: |
||||
|
return int(duration.Hours() / 24) |
||||
|
} |
||||
|
} |
||||
|
if seconds, ok := value.(int); ok { |
||||
|
switch f.Unit { |
||||
|
case UnitMinutes: |
||||
|
return seconds / 60 |
||||
|
case UnitHours: |
||||
|
return seconds / 3600 |
||||
|
case UnitDays: |
||||
|
return seconds / (24 * 3600) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return value |
||||
|
} |
||||
|
|
||||
|
// GetIntervalDisplayValue returns the value and unit for interval fields
|
||||
|
func (f *Field) GetIntervalDisplayValue(value interface{}) (int, string) { |
||||
|
if f.Type != FieldTypeInterval { |
||||
|
return 0, "minutes" |
||||
|
} |
||||
|
|
||||
|
seconds := 0 |
||||
|
if duration, ok := value.(time.Duration); ok { |
||||
|
seconds = int(duration.Seconds()) |
||||
|
} else if s, ok := value.(int); ok { |
||||
|
seconds = s |
||||
|
} |
||||
|
|
||||
|
return SecondsToIntervalValueUnit(seconds) |
||||
|
} |
||||
|
|
||||
|
// SecondsToIntervalValueUnit converts seconds to the most appropriate interval unit
|
||||
|
func SecondsToIntervalValueUnit(totalSeconds int) (int, string) { |
||||
|
if totalSeconds == 0 { |
||||
|
return 0, "minutes" |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by days
|
||||
|
if totalSeconds%(24*3600) == 0 { |
||||
|
return totalSeconds / (24 * 3600), "days" |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by hours
|
||||
|
if totalSeconds%3600 == 0 { |
||||
|
return totalSeconds / 3600, "hours" |
||||
|
} |
||||
|
|
||||
|
// Default to minutes
|
||||
|
return totalSeconds / 60, "minutes" |
||||
|
} |
||||
|
|
||||
|
// IntervalValueUnitToSeconds converts interval value and unit to seconds
|
||||
|
func IntervalValueUnitToSeconds(value int, unit string) int { |
||||
|
switch unit { |
||||
|
case "days": |
||||
|
return value * 24 * 3600 |
||||
|
case "hours": |
||||
|
return value * 3600 |
||||
|
case "minutes": |
||||
|
return value * 60 |
||||
|
default: |
||||
|
return value * 60 // Default to minutes
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ParseDisplayValue converts a display value back to the storage format
|
||||
|
func (f *Field) ParseDisplayValue(displayValue interface{}) interface{} { |
||||
|
if (f.Type == FieldTypeDuration || f.Type == FieldTypeInterval) && f.Unit != UnitSeconds { |
||||
|
if val, ok := displayValue.(int); ok { |
||||
|
switch f.Unit { |
||||
|
case UnitMinutes: |
||||
|
return val * 60 |
||||
|
case UnitHours: |
||||
|
return val * 3600 |
||||
|
case UnitDays: |
||||
|
return val * 24 * 3600 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return displayValue |
||||
|
} |
||||
|
|
||||
|
// ParseIntervalFormData parses form data for interval fields (value + unit)
|
||||
|
func (f *Field) ParseIntervalFormData(valueStr, unitStr string) (int, error) { |
||||
|
if f.Type != FieldTypeInterval { |
||||
|
return 0, fmt.Errorf("field %s is not an interval field", f.Name) |
||||
|
} |
||||
|
|
||||
|
value := 0 |
||||
|
if valueStr != "" { |
||||
|
var err error |
||||
|
value, err = fmt.Sscanf(valueStr, "%d", &value) |
||||
|
if err != nil { |
||||
|
return 0, fmt.Errorf("invalid interval value: %s", valueStr) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return IntervalValueUnitToSeconds(value, unitStr), nil |
||||
|
} |
||||
|
|
||||
|
// ValidateValue validates a value against the field constraints
|
||||
|
func (f *Field) ValidateValue(value interface{}) error { |
||||
|
if f.Required && (value == nil || value == "" || value == 0) { |
||||
|
return fmt.Errorf("%s is required", f.DisplayName) |
||||
|
} |
||||
|
|
||||
|
if f.MinValue != nil { |
||||
|
if !f.compareValues(value, f.MinValue, ">=") { |
||||
|
return fmt.Errorf("%s must be >= %v", f.DisplayName, f.MinValue) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if f.MaxValue != nil { |
||||
|
if !f.compareValues(value, f.MaxValue, "<=") { |
||||
|
return fmt.Errorf("%s must be <= %v", f.DisplayName, f.MaxValue) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// compareValues compares two values based on the operator
|
||||
|
func (f *Field) compareValues(a, b interface{}, op string) bool { |
||||
|
switch f.Type { |
||||
|
case FieldTypeInt: |
||||
|
aVal, aOk := a.(int) |
||||
|
bVal, bOk := b.(int) |
||||
|
if !aOk || !bOk { |
||||
|
return false |
||||
|
} |
||||
|
switch op { |
||||
|
case ">=": |
||||
|
return aVal >= bVal |
||||
|
case "<=": |
||||
|
return aVal <= bVal |
||||
|
} |
||||
|
case FieldTypeFloat: |
||||
|
aVal, aOk := a.(float64) |
||||
|
bVal, bOk := b.(float64) |
||||
|
if !aOk || !bOk { |
||||
|
return false |
||||
|
} |
||||
|
switch op { |
||||
|
case ">=": |
||||
|
return aVal >= bVal |
||||
|
case "<=": |
||||
|
return aVal <= bVal |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
// Schema provides common functionality for configuration schemas
|
||||
|
type Schema struct { |
||||
|
Fields []*Field `json:"fields"` |
||||
|
} |
||||
|
|
||||
|
// GetFieldByName returns a field by its JSON name
|
||||
|
func (s *Schema) GetFieldByName(jsonName string) *Field { |
||||
|
for _, field := range s.Fields { |
||||
|
if field.JSONName == jsonName { |
||||
|
return field |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// ApplyDefaults applies default values to a configuration struct using reflection
|
||||
|
func (s *Schema) ApplyDefaults(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) |
||||
|
|
||||
|
// 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 := s.GetFieldByName(jsonTag) |
||||
|
if schemaField == nil { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
// Apply default if field is zero value
|
||||
|
if field.CanSet() && isZeroValue(field) { |
||||
|
defaultValue := reflect.ValueOf(schemaField.DefaultValue) |
||||
|
if defaultValue.Type().ConvertibleTo(field.Type()) { |
||||
|
field.Set(defaultValue.Convert(field.Type())) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// ValidateConfig validates a configuration against the schema
|
||||
|
func (s *Schema) ValidateConfig(config interface{}) []error { |
||||
|
var errors []error |
||||
|
|
||||
|
configValue := reflect.ValueOf(config) |
||||
|
if configValue.Kind() == reflect.Ptr { |
||||
|
configValue = configValue.Elem() |
||||
|
} |
||||
|
|
||||
|
if configValue.Kind() != reflect.Struct { |
||||
|
errors = append(errors, fmt.Errorf("config must be a struct or pointer to struct")) |
||||
|
return errors |
||||
|
} |
||||
|
|
||||
|
configType := configValue.Type() |
||||
|
|
||||
|
for i := 0; i < configValue.NumField(); i++ { |
||||
|
field := configValue.Field(i) |
||||
|
fieldType := configType.Field(i) |
||||
|
|
||||
|
// 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 := s.GetFieldByName(jsonTag) |
||||
|
if schemaField == nil { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
// Validate field value
|
||||
|
fieldValue := field.Interface() |
||||
|
if err := schemaField.ValidateValue(fieldValue); err != nil { |
||||
|
errors = append(errors, err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return errors |
||||
|
} |
||||
|
|
||||
|
// isZeroValue checks if a reflect.Value represents a zero value
|
||||
|
func isZeroValue(v reflect.Value) bool { |
||||
|
switch v.Kind() { |
||||
|
case reflect.Bool: |
||||
|
return !v.Bool() |
||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||
|
return v.Int() == 0 |
||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||
|
return v.Uint() == 0 |
||||
|
case reflect.Float32, reflect.Float64: |
||||
|
return v.Float() == 0 |
||||
|
case reflect.String: |
||||
|
return v.String() == "" |
||||
|
case reflect.Slice, reflect.Map, reflect.Array: |
||||
|
return v.IsNil() || v.Len() == 0 |
||||
|
case reflect.Interface, reflect.Ptr: |
||||
|
return v.IsNil() |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
554
weed/admin/view/app/maintenance_config_schema_templ.go
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,396 @@ |
|||||
|
package app |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"reflect" |
||||
|
"strings" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/admin/maintenance" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/worker/tasks" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/admin/config" |
||||
|
) |
||||
|
|
||||
|
templ TaskConfigSchema(data *maintenance.TaskConfigData, schema *tasks.TaskConfigSchema, config interface{}) { |
||||
|
<div class="container-fluid"> |
||||
|
<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={schema.Icon + " me-2"}></i> |
||||
|
{schema.DisplayName} Configuration |
||||
|
</h2> |
||||
|
<div class="btn-group"> |
||||
|
<a href="/maintenance/config" class="btn btn-outline-secondary"> |
||||
|
<i class="fas fa-arrow-left me-1"></i> |
||||
|
Back to System Config |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col-12"> |
||||
|
<div class="card"> |
||||
|
<div class="card-header"> |
||||
|
<h5 class="mb-0">{schema.DisplayName} Settings</h5> |
||||
|
<p class="mb-0 text-muted">{schema.Description}</p> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
<form id="taskConfigForm" method="POST"> |
||||
|
<!-- Dynamically render all schema fields in defined order --> |
||||
|
for _, field := range schema.Fields { |
||||
|
@TaskConfigField(field, getTaskFieldValue(config, field.JSONName)) |
||||
|
} |
||||
|
|
||||
|
<div class="d-flex gap-2"> |
||||
|
<button type="submit" class="btn btn-primary"> |
||||
|
<i class="fas fa-save me-1"></i> |
||||
|
Save Configuration |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-secondary" onclick="resetToDefaults()"> |
||||
|
<i class="fas fa-undo me-1"></i> |
||||
|
Reset to Defaults |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Performance Notes Card --> |
||||
|
<div class="row mt-4"> |
||||
|
<div class="col-12"> |
||||
|
<div class="card"> |
||||
|
<div class="card-header"> |
||||
|
<h5 class="mb-0"> |
||||
|
<i class="fas fa-info-circle me-2"></i> |
||||
|
Important Notes |
||||
|
</h5> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
<div class="alert alert-info" role="alert"> |
||||
|
if schema.TaskName == "vacuum" { |
||||
|
<h6 class="alert-heading">Vacuum Operations:</h6> |
||||
|
<p class="mb-2"><strong>Performance:</strong> Vacuum operations are I/O intensive and may impact cluster performance.</p> |
||||
|
<p class="mb-2"><strong>Safety:</strong> Only volumes meeting age and garbage thresholds will be processed.</p> |
||||
|
<p class="mb-0"><strong>Recommendation:</strong> Monitor cluster load and adjust concurrent limits accordingly.</p> |
||||
|
} else if schema.TaskName == "balance" { |
||||
|
<h6 class="alert-heading">Balance Operations:</h6> |
||||
|
<p class="mb-2"><strong>Performance:</strong> Volume balancing involves data movement and can impact cluster performance.</p> |
||||
|
<p class="mb-2"><strong>Safety:</strong> Requires adequate server count to ensure data safety during moves.</p> |
||||
|
<p class="mb-0"><strong>Recommendation:</strong> Run during off-peak hours to minimize impact on production workloads.</p> |
||||
|
} else if schema.TaskName == "erasure_coding" { |
||||
|
<h6 class="alert-heading">Erasure Coding Operations:</h6> |
||||
|
<p class="mb-2"><strong>Performance:</strong> Erasure coding is CPU and I/O intensive. Consider running during off-peak hours.</p> |
||||
|
<p class="mb-2"><strong>Durability:</strong> With 10+4 configuration, can tolerate up to 4 shard failures.</p> |
||||
|
<p class="mb-0"><strong>Configuration:</strong> Fullness ratio should be between 0.5 and 1.0 (e.g., 0.90 for 90%).</p> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<script> |
||||
|
function resetToDefaults() { |
||||
|
if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) { |
||||
|
// Reset form fields to their default values |
||||
|
const form = document.getElementById('taskConfigForm'); |
||||
|
const schemaFields = window.taskConfigSchema ? window.taskConfigSchema.fields : {}; |
||||
|
|
||||
|
Object.keys(schemaFields).forEach(fieldName => { |
||||
|
const field = schemaFields[fieldName]; |
||||
|
const element = document.getElementById(fieldName); |
||||
|
|
||||
|
if (element && field.default_value !== undefined) { |
||||
|
if (field.input_type === 'checkbox') { |
||||
|
element.checked = field.default_value; |
||||
|
} else if (field.input_type === 'interval') { |
||||
|
// Handle interval fields with value and unit |
||||
|
const valueElement = document.getElementById(fieldName + '_value'); |
||||
|
const unitElement = document.getElementById(fieldName + '_unit'); |
||||
|
if (valueElement && unitElement && field.default_value) { |
||||
|
const defaultSeconds = field.default_value; |
||||
|
const { value, unit } = convertSecondsToTaskIntervalValueUnit(defaultSeconds); |
||||
|
valueElement.value = value; |
||||
|
unitElement.value = unit; |
||||
|
} |
||||
|
} else { |
||||
|
element.value = field.default_value; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function convertSecondsToTaskIntervalValueUnit(totalSeconds) { |
||||
|
if (totalSeconds === 0) { |
||||
|
return { value: 0, unit: 'minutes' }; |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by days |
||||
|
if (totalSeconds % (24 * 3600) === 0) { |
||||
|
return { value: totalSeconds / (24 * 3600), unit: 'days' }; |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by hours |
||||
|
if (totalSeconds % 3600 === 0) { |
||||
|
return { value: totalSeconds / 3600, unit: 'hours' }; |
||||
|
} |
||||
|
|
||||
|
// Default to minutes |
||||
|
return { value: totalSeconds / 60, unit: 'minutes' }; |
||||
|
} |
||||
|
|
||||
|
// Store schema data for JavaScript access |
||||
|
window.taskConfigSchema = @taskSchemaToTaskJSON(schema); |
||||
|
</script> |
||||
|
} |
||||
|
|
||||
|
// TaskConfigField renders a single task configuration field based on schema |
||||
|
templ TaskConfigField(field *config.Field, value interface{}) { |
||||
|
if field.InputType == "interval" { |
||||
|
<!-- Interval field with number input + unit dropdown --> |
||||
|
<div class="mb-3"> |
||||
|
<label for={ field.JSONName } class="form-label"> |
||||
|
{ field.DisplayName } |
||||
|
if field.Required { |
||||
|
<span class="text-danger">*</span> |
||||
|
} |
||||
|
</label> |
||||
|
<div class="input-group"> |
||||
|
<input |
||||
|
type="number" |
||||
|
class="form-control" |
||||
|
id={ field.JSONName + "_value" } |
||||
|
name={ field.JSONName + "_value" } |
||||
|
value={ fmt.Sprintf("%.0f", convertTaskSecondsToDisplayValue(value, field)) } |
||||
|
step="1" |
||||
|
min="1" |
||||
|
if field.Required { |
||||
|
required |
||||
|
} |
||||
|
/> |
||||
|
<select |
||||
|
class="form-select" |
||||
|
id={ field.JSONName + "_unit" } |
||||
|
name={ field.JSONName + "_unit" } |
||||
|
style="max-width: 120px;" |
||||
|
if field.Required { |
||||
|
required |
||||
|
} |
||||
|
> |
||||
|
<option |
||||
|
value="minutes" |
||||
|
if getTaskDisplayUnit(value, field) == "minutes" { |
||||
|
selected |
||||
|
} |
||||
|
> |
||||
|
Minutes |
||||
|
</option> |
||||
|
<option |
||||
|
value="hours" |
||||
|
if getTaskDisplayUnit(value, field) == "hours" { |
||||
|
selected |
||||
|
} |
||||
|
> |
||||
|
Hours |
||||
|
</option> |
||||
|
<option |
||||
|
value="days" |
||||
|
if getTaskDisplayUnit(value, field) == "days" { |
||||
|
selected |
||||
|
} |
||||
|
> |
||||
|
Days |
||||
|
</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
if field.Description != "" { |
||||
|
<div class="form-text text-muted">{ field.Description }</div> |
||||
|
} |
||||
|
</div> |
||||
|
} else if field.InputType == "checkbox" { |
||||
|
<!-- Checkbox field --> |
||||
|
<div class="mb-3"> |
||||
|
<div class="form-check form-switch"> |
||||
|
<input |
||||
|
class="form-check-input" |
||||
|
type="checkbox" |
||||
|
id={ field.JSONName } |
||||
|
name={ field.JSONName } |
||||
|
if getTaskBoolValue(value) { |
||||
|
checked |
||||
|
} |
||||
|
/> |
||||
|
<label class="form-check-label" for={ field.JSONName }> |
||||
|
<strong>{ field.DisplayName }</strong> |
||||
|
</label> |
||||
|
</div> |
||||
|
if field.Description != "" { |
||||
|
<div class="form-text text-muted">{ field.Description }</div> |
||||
|
} |
||||
|
</div> |
||||
|
} else if field.InputType == "text" { |
||||
|
<!-- Text field --> |
||||
|
<div class="mb-3"> |
||||
|
<label for={ field.JSONName } class="form-label"> |
||||
|
{ field.DisplayName } |
||||
|
if field.Required { |
||||
|
<span class="text-danger">*</span> |
||||
|
} |
||||
|
</label> |
||||
|
<input |
||||
|
type="text" |
||||
|
class="form-control" |
||||
|
id={ field.JSONName } |
||||
|
name={ field.JSONName } |
||||
|
value={ fmt.Sprintf("%v", value) } |
||||
|
placeholder={ field.Placeholder } |
||||
|
if field.Required { |
||||
|
required |
||||
|
} |
||||
|
/> |
||||
|
if field.Description != "" { |
||||
|
<div class="form-text text-muted">{ field.Description }</div> |
||||
|
} |
||||
|
</div> |
||||
|
} else { |
||||
|
<!-- Number field --> |
||||
|
<div class="mb-3"> |
||||
|
<label for={ field.JSONName } class="form-label"> |
||||
|
{ field.DisplayName } |
||||
|
if field.Required { |
||||
|
<span class="text-danger">*</span> |
||||
|
} |
||||
|
</label> |
||||
|
<input |
||||
|
type="number" |
||||
|
class="form-control" |
||||
|
id={ field.JSONName } |
||||
|
name={ field.JSONName } |
||||
|
value={ fmt.Sprintf("%v", value) } |
||||
|
placeholder={ field.Placeholder } |
||||
|
if field.MinValue != nil { |
||||
|
min={ fmt.Sprintf("%v", field.MinValue) } |
||||
|
} |
||||
|
if field.MaxValue != nil { |
||||
|
max={ fmt.Sprintf("%v", field.MaxValue) } |
||||
|
} |
||||
|
step={ getTaskNumberStep(field) } |
||||
|
if field.Required { |
||||
|
required |
||||
|
} |
||||
|
/> |
||||
|
if field.Description != "" { |
||||
|
<div class="form-text text-muted">{ field.Description }</div> |
||||
|
} |
||||
|
</div> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Helper functions for the template |
||||
|
func getTaskBoolValue(value interface{}) bool { |
||||
|
if boolVal, ok := value.(bool); ok { |
||||
|
return boolVal |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func convertTaskSecondsToDisplayValue(value interface{}, field *config.Field) float64 { |
||||
|
if intVal, ok := value.(int); ok { |
||||
|
if intVal == 0 { |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by days |
||||
|
if intVal%(24*3600) == 0 { |
||||
|
return float64(intVal / (24 * 3600)) |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by hours |
||||
|
if intVal%3600 == 0 { |
||||
|
return float64(intVal / 3600) |
||||
|
} |
||||
|
|
||||
|
// Default to minutes |
||||
|
return float64(intVal / 60) |
||||
|
} |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
func getTaskDisplayUnit(value interface{}, field *config.Field) string { |
||||
|
if intVal, ok := value.(int); ok { |
||||
|
if intVal == 0 { |
||||
|
return "minutes" |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by days |
||||
|
if intVal%(24*3600) == 0 { |
||||
|
return "days" |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by hours |
||||
|
if intVal%3600 == 0 { |
||||
|
return "hours" |
||||
|
} |
||||
|
|
||||
|
// Default to minutes |
||||
|
return "minutes" |
||||
|
} |
||||
|
return "minutes" |
||||
|
} |
||||
|
|
||||
|
func getTaskNumberStep(field *config.Field) string { |
||||
|
if field.Type == config.FieldTypeFloat { |
||||
|
return "0.01" |
||||
|
} |
||||
|
return "1" |
||||
|
} |
||||
|
|
||||
|
func getTaskFieldValue(config interface{}, fieldName string) interface{} { |
||||
|
if config == nil { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Use reflection to get the field value from the config struct |
||||
|
configValue := reflect.ValueOf(config) |
||||
|
if configValue.Kind() == reflect.Ptr { |
||||
|
configValue = configValue.Elem() |
||||
|
} |
||||
|
|
||||
|
if configValue.Kind() != reflect.Struct { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
configType := configValue.Type() |
||||
|
|
||||
|
for i := 0; i < configValue.NumField(); i++ { |
||||
|
field := configValue.Field(i) |
||||
|
fieldType := configType.Field(i) |
||||
|
|
||||
|
// 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] |
||||
|
} |
||||
|
|
||||
|
if jsonTag == fieldName { |
||||
|
return field.Interface() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Helper function to convert schema to JSON for JavaScript |
||||
|
templ taskSchemaToTaskJSON(schema *tasks.TaskConfigSchema) { |
||||
|
{`{}`} |
||||
|
} |
||||
@ -0,0 +1,877 @@ |
|||||
|
// Code generated by templ - DO NOT EDIT.
|
||||
|
|
||||
|
// templ: version: v0.3.906
|
||||
|
package app |
||||
|
|
||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
|
||||
|
import "github.com/a-h/templ" |
||||
|
import templruntime "github.com/a-h/templ/runtime" |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/admin/config" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/admin/maintenance" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/worker/tasks" |
||||
|
"reflect" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
func TaskConfigSchema(data *maintenance.TaskConfigData, schema *tasks.TaskConfigSchema, config interface{}) templ.Component { |
||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { |
||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context |
||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { |
||||
|
return templ_7745c5c3_CtxErr |
||||
|
} |
||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) |
||||
|
if !templ_7745c5c3_IsBuffer { |
||||
|
defer func() { |
||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) |
||||
|
if templ_7745c5c3_Err == nil { |
||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr |
||||
|
} |
||||
|
}() |
||||
|
} |
||||
|
ctx = templ.InitializeContext(ctx) |
||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx) |
||||
|
if templ_7745c5c3_Var1 == nil { |
||||
|
templ_7745c5c3_Var1 = templ.NopComponent |
||||
|
} |
||||
|
ctx = templ.ClearChildren(ctx) |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container-fluid\"><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"d-flex justify-content-between align-items-center\"><h2 class=\"mb-0\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var2 = []any{schema.Icon + " me-2"} |
||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<i class=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var3 string |
||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 1, Col: 0} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"></i> ") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var4 string |
||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(schema.DisplayName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 19, Col: 43} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " Configuration</h2><div class=\"btn-group\"><a href=\"/maintenance/config\" class=\"btn btn-outline-secondary\"><i class=\"fas fa-arrow-left me-1\"></i> Back to System Config</a></div></div></div></div><div class=\"row\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var5 string |
||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(schema.DisplayName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 35, Col: 60} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " Settings</h5><p class=\"mb-0 text-muted\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var6 string |
||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(schema.Description) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 36, Col: 70} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p></div><div class=\"card-body\"><form id=\"taskConfigForm\" method=\"POST\"><!-- Dynamically render all schema fields in defined order -->") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
for _, field := range schema.Fields { |
||||
|
templ_7745c5c3_Err = TaskConfigField(field, getTaskFieldValue(config, field.JSONName)).Render(ctx, templ_7745c5c3_Buffer) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"d-flex gap-2\"><button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-save me-1\"></i> Save Configuration</button> <button type=\"button\" class=\"btn btn-secondary\" onclick=\"resetToDefaults()\"><i class=\"fas fa-undo me-1\"></i> Reset to Defaults</button></div></form></div></div></div></div><!-- Performance Notes Card --><div class=\"row mt-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\"><i class=\"fas fa-info-circle me-2\"></i> Important Notes</h5></div><div class=\"card-body\"><div class=\"alert alert-info\" role=\"alert\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if schema.TaskName == "vacuum" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<h6 class=\"alert-heading\">Vacuum Operations:</h6><p class=\"mb-2\"><strong>Performance:</strong> Vacuum operations are I/O intensive and may impact cluster performance.</p><p class=\"mb-2\"><strong>Safety:</strong> Only volumes meeting age and garbage thresholds will be processed.</p><p class=\"mb-0\"><strong>Recommendation:</strong> Monitor cluster load and adjust concurrent limits accordingly.</p>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} else if schema.TaskName == "balance" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<h6 class=\"alert-heading\">Balance Operations:</h6><p class=\"mb-2\"><strong>Performance:</strong> Volume balancing involves data movement and can impact cluster performance.</p><p class=\"mb-2\"><strong>Safety:</strong> Requires adequate server count to ensure data safety during moves.</p><p class=\"mb-0\"><strong>Recommendation:</strong> Run during off-peak hours to minimize impact on production workloads.</p>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} else if schema.TaskName == "erasure_coding" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h6 class=\"alert-heading\">Erasure Coding Operations:</h6><p class=\"mb-2\"><strong>Performance:</strong> Erasure coding is CPU and I/O intensive. Consider running during off-peak hours.</p><p class=\"mb-2\"><strong>Durability:</strong> With 10+4 configuration, can tolerate up to 4 shard failures.</p><p class=\"mb-0\"><strong>Configuration:</strong> Fullness ratio should be between 0.5 and 1.0 (e.g., 0.90 for 90%).</p>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></div></div></div></div></div><script>\n function resetToDefaults() {\n if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {\n // Reset form fields to their default values\n const form = document.getElementById('taskConfigForm');\n const schemaFields = window.taskConfigSchema ? window.taskConfigSchema.fields : {};\n \n Object.keys(schemaFields).forEach(fieldName => {\n const field = schemaFields[fieldName];\n const element = document.getElementById(fieldName);\n \n if (element && field.default_value !== undefined) {\n if (field.input_type === 'checkbox') {\n element.checked = field.default_value;\n } else if (field.input_type === 'interval') {\n // Handle interval fields with value and unit\n const valueElement = document.getElementById(fieldName + '_value');\n const unitElement = document.getElementById(fieldName + '_unit');\n if (valueElement && unitElement && field.default_value) {\n const defaultSeconds = field.default_value;\n const { value, unit } = convertSecondsToTaskIntervalValueUnit(defaultSeconds);\n valueElement.value = value;\n unitElement.value = unit;\n }\n } else {\n element.value = field.default_value;\n }\n }\n });\n }\n }\n\n function convertSecondsToTaskIntervalValueUnit(totalSeconds) {\n if (totalSeconds === 0) {\n return { value: 0, unit: 'minutes' };\n }\n\n // Check if it's evenly divisible by days\n if (totalSeconds % (24 * 3600) === 0) {\n return { value: totalSeconds / (24 * 3600), unit: 'days' };\n }\n\n // Check if it's evenly divisible by hours\n if (totalSeconds % 3600 === 0) {\n return { value: totalSeconds / 3600, unit: 'hours' };\n }\n\n // Default to minutes\n return { value: totalSeconds / 60, unit: 'minutes' };\n }\n\n // Store schema data for JavaScript access\n window.taskConfigSchema = @taskSchemaToTaskJSON(schema);\n </script>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// TaskConfigField renders a single task configuration field based on schema
|
||||
|
func TaskConfigField(field *config.Field, value interface{}) templ.Component { |
||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { |
||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context |
||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { |
||||
|
return templ_7745c5c3_CtxErr |
||||
|
} |
||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) |
||||
|
if !templ_7745c5c3_IsBuffer { |
||||
|
defer func() { |
||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) |
||||
|
if templ_7745c5c3_Err == nil { |
||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr |
||||
|
} |
||||
|
}() |
||||
|
} |
||||
|
ctx = templ.InitializeContext(ctx) |
||||
|
templ_7745c5c3_Var7 := templ.GetChildren(ctx) |
||||
|
if templ_7745c5c3_Var7 == nil { |
||||
|
templ_7745c5c3_Var7 = templ.NopComponent |
||||
|
} |
||||
|
ctx = templ.ClearChildren(ctx) |
||||
|
if field.InputType == "interval" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<!-- Interval field with number input + unit dropdown --> <div class=\"mb-3\"><label for=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var8 string |
||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 157, Col: 39} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"form-label\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var9 string |
||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(field.DisplayName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 158, Col: 35} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span class=\"text-danger\">*</span>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</label><div class=\"input-group\"><input type=\"number\" class=\"form-control\" id=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var10 string |
||||
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName + "_value") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 167, Col: 50} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" name=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var11 string |
||||
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName + "_value") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 168, Col: 52} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" value=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var12 string |
||||
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f", convertTaskSecondsToDisplayValue(value, field))) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 169, Col: 95} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" step=\"1\" min=\"1\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " required") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "> <select class=\"form-select\" id=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var13 string |
||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName + "_unit") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 178, Col: 49} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" name=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var14 string |
||||
|
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName + "_unit") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 179, Col: 51} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" style=\"max-width: 120px;\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " required") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "><option value=\"minutes\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if getTaskDisplayUnit(value, field) == "minutes" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " selected") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, ">Minutes</option> <option value=\"hours\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if getTaskDisplayUnit(value, field) == "hours" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " selected") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, ">Hours</option> <option value=\"days\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if getTaskDisplayUnit(value, field) == "days" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " selected") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, ">Days</option></select></div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Description != "" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div class=\"form-text text-muted\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var15 string |
||||
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(field.Description) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 212, Col: 69} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} else if field.InputType == "checkbox" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<!-- Checkbox field --> <div class=\"mb-3\"><div class=\"form-check form-switch\"><input class=\"form-check-input\" type=\"checkbox\" id=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var16 string |
||||
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 222, Col: 39} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" name=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var17 string |
||||
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 223, Col: 41} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if getTaskBoolValue(value) { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " checked") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "> <label class=\"form-check-label\" for=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var18 string |
||||
|
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 228, Col: 68} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\"><strong>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var19 string |
||||
|
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(field.DisplayName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 229, Col: 47} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</strong></label></div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Description != "" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"form-text text-muted\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var20 string |
||||
|
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(field.Description) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 233, Col: 69} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} else if field.InputType == "text" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<!-- Text field --> <div class=\"mb-3\"><label for=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var21 string |
||||
|
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 239, Col: 39} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\" class=\"form-label\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var22 string |
||||
|
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(field.DisplayName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 240, Col: 35} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " ") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<span class=\"text-danger\">*</span>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</label> <input type=\"text\" class=\"form-control\" id=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var23 string |
||||
|
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 248, Col: 35} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\" name=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var24 string |
||||
|
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 249, Col: 37} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\" value=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var25 string |
||||
|
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", value)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 250, Col: 48} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" placeholder=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var26 string |
||||
|
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(field.Placeholder) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 251, Col: 47} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, " required") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "> ") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Description != "" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<div class=\"form-text text-muted\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var27 string |
||||
|
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(field.Description) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 257, Col: 69} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} else { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<!-- Number field --> <div class=\"mb-3\"><label for=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var28 string |
||||
|
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 263, Col: 39} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\" class=\"form-label\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var29 string |
||||
|
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(field.DisplayName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 264, Col: 35} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " ") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<span class=\"text-danger\">*</span>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</label> <input type=\"number\" class=\"form-control\" id=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var30 string |
||||
|
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 272, Col: 35} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\" name=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var31 string |
||||
|
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(field.JSONName) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 273, Col: 37} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\" value=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var32 string |
||||
|
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", value)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 274, Col: 48} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\" placeholder=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var33 string |
||||
|
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(field.Placeholder) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 275, Col: 47} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.MinValue != nil { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, " min=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var34 string |
||||
|
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", field.MinValue)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 277, Col: 59} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
if field.MaxValue != nil { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, " max=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var35 string |
||||
|
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", field.MaxValue)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 280, Col: 59} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, " step=\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var36 string |
||||
|
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(getTaskNumberStep(field)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 282, Col: 47} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\"") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Required { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, " required") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "> ") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
if field.Description != "" { |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<div class=\"form-text text-muted\">") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
var templ_7745c5c3_Var37 string |
||||
|
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(field.Description) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 288, Col: 69} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</div>") |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// Helper functions for the template
|
||||
|
func getTaskBoolValue(value interface{}) bool { |
||||
|
if boolVal, ok := value.(bool); ok { |
||||
|
return boolVal |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func convertTaskSecondsToDisplayValue(value interface{}, field *config.Field) float64 { |
||||
|
if intVal, ok := value.(int); ok { |
||||
|
if intVal == 0 { |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by days
|
||||
|
if intVal%(24*3600) == 0 { |
||||
|
return float64(intVal / (24 * 3600)) |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by hours
|
||||
|
if intVal%3600 == 0 { |
||||
|
return float64(intVal / 3600) |
||||
|
} |
||||
|
|
||||
|
// Default to minutes
|
||||
|
return float64(intVal / 60) |
||||
|
} |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
func getTaskDisplayUnit(value interface{}, field *config.Field) string { |
||||
|
if intVal, ok := value.(int); ok { |
||||
|
if intVal == 0 { |
||||
|
return "minutes" |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by days
|
||||
|
if intVal%(24*3600) == 0 { |
||||
|
return "days" |
||||
|
} |
||||
|
|
||||
|
// Check if it's evenly divisible by hours
|
||||
|
if intVal%3600 == 0 { |
||||
|
return "hours" |
||||
|
} |
||||
|
|
||||
|
// Default to minutes
|
||||
|
return "minutes" |
||||
|
} |
||||
|
return "minutes" |
||||
|
} |
||||
|
|
||||
|
func getTaskNumberStep(field *config.Field) string { |
||||
|
if field.Type == config.FieldTypeFloat { |
||||
|
return "0.01" |
||||
|
} |
||||
|
return "1" |
||||
|
} |
||||
|
|
||||
|
func getTaskFieldValue(config interface{}, fieldName string) interface{} { |
||||
|
if config == nil { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Use reflection to get the field value from the config struct
|
||||
|
configValue := reflect.ValueOf(config) |
||||
|
if configValue.Kind() == reflect.Ptr { |
||||
|
configValue = configValue.Elem() |
||||
|
} |
||||
|
|
||||
|
if configValue.Kind() != reflect.Struct { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
configType := configValue.Type() |
||||
|
|
||||
|
for i := 0; i < configValue.NumField(); i++ { |
||||
|
field := configValue.Field(i) |
||||
|
fieldType := configType.Field(i) |
||||
|
|
||||
|
// 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] |
||||
|
} |
||||
|
|
||||
|
if jsonTag == fieldName { |
||||
|
return field.Interface() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Helper function to convert schema to JSON for JavaScript
|
||||
|
func taskSchemaToTaskJSON(schema *tasks.TaskConfigSchema) templ.Component { |
||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { |
||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context |
||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { |
||||
|
return templ_7745c5c3_CtxErr |
||||
|
} |
||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) |
||||
|
if !templ_7745c5c3_IsBuffer { |
||||
|
defer func() { |
||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) |
||||
|
if templ_7745c5c3_Err == nil { |
||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr |
||||
|
} |
||||
|
}() |
||||
|
} |
||||
|
ctx = templ.InitializeContext(ctx) |
||||
|
templ_7745c5c3_Var38 := templ.GetChildren(ctx) |
||||
|
if templ_7745c5c3_Var38 == nil { |
||||
|
templ_7745c5c3_Var38 = templ.NopComponent |
||||
|
} |
||||
|
ctx = templ.ClearChildren(ctx) |
||||
|
var templ_7745c5c3_Var39 string |
||||
|
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(`{}`) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/task_config_schema.templ`, Line: 395, Col: 9} |
||||
|
} |
||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) |
||||
|
if templ_7745c5c3_Err != nil { |
||||
|
return templ_7745c5c3_Err |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
var _ = templruntime.GeneratedTemplate |
||||
@ -0,0 +1,310 @@ |
|||||
|
package tasks |
||||
|
|
||||
|
import ( |
||||
|
"github.com/seaweedfs/seaweedfs/weed/admin/config" |
||||
|
) |
||||
|
|
||||
|
// TaskConfigSchema defines the schema for task configuration
|
||||
|
type TaskConfigSchema struct { |
||||
|
config.Schema // Embed common schema functionality
|
||||
|
TaskName string `json:"task_name"` |
||||
|
DisplayName string `json:"display_name"` |
||||
|
Description string `json:"description"` |
||||
|
Icon string `json:"icon"` |
||||
|
} |
||||
|
|
||||
|
// GetTaskConfigSchema returns the schema for the specified task type
|
||||
|
func GetTaskConfigSchema(taskType string) *TaskConfigSchema { |
||||
|
switch taskType { |
||||
|
case "vacuum": |
||||
|
return GetVacuumTaskConfigSchema() |
||||
|
case "balance": |
||||
|
return GetBalanceTaskConfigSchema() |
||||
|
case "erasure_coding": |
||||
|
return GetErasureCodingTaskConfigSchema() |
||||
|
default: |
||||
|
return nil |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// GetVacuumTaskConfigSchema returns the schema for vacuum task configuration
|
||||
|
func GetVacuumTaskConfigSchema() *TaskConfigSchema { |
||||
|
return &TaskConfigSchema{ |
||||
|
TaskName: "vacuum", |
||||
|
DisplayName: "Volume Vacuum", |
||||
|
Description: "Reclaims disk space by removing deleted files from volumes", |
||||
|
Icon: "fas fa-broom text-primary", |
||||
|
Schema: config.Schema{ |
||||
|
Fields: []*config.Field{ |
||||
|
{ |
||||
|
Name: "enabled", |
||||
|
JSONName: "enabled", |
||||
|
Type: config.FieldTypeBool, |
||||
|
DefaultValue: true, |
||||
|
Required: false, |
||||
|
DisplayName: "Enable Vacuum Tasks", |
||||
|
Description: "Whether vacuum tasks should be automatically created", |
||||
|
HelpText: "Toggle this to enable or disable automatic vacuum task generation", |
||||
|
InputType: "checkbox", |
||||
|
CSSClasses: "form-check-input", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "garbage_threshold", |
||||
|
JSONName: "garbage_threshold", |
||||
|
Type: config.FieldTypeFloat, |
||||
|
DefaultValue: 0.3, // 30%
|
||||
|
MinValue: 0.0, |
||||
|
MaxValue: 1.0, |
||||
|
Required: true, |
||||
|
DisplayName: "Garbage Percentage Threshold", |
||||
|
Description: "Trigger vacuum when garbage ratio exceeds this percentage", |
||||
|
HelpText: "Volumes with more deleted content than this threshold will be vacuumed", |
||||
|
Placeholder: "0.30 (30%)", |
||||
|
Unit: config.UnitNone, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "scan_interval_seconds", |
||||
|
JSONName: "scan_interval_seconds", |
||||
|
Type: config.FieldTypeInterval, |
||||
|
DefaultValue: 2 * 60 * 60, // 2 hours
|
||||
|
MinValue: 10 * 60, // 10 minutes
|
||||
|
MaxValue: 24 * 60 * 60, // 24 hours
|
||||
|
Required: true, |
||||
|
DisplayName: "Scan Interval", |
||||
|
Description: "How often to scan for volumes needing vacuum", |
||||
|
HelpText: "The system will check for volumes that need vacuuming at this interval", |
||||
|
Placeholder: "2", |
||||
|
Unit: config.UnitHours, |
||||
|
InputType: "interval", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "max_concurrent", |
||||
|
JSONName: "max_concurrent", |
||||
|
Type: config.FieldTypeInt, |
||||
|
DefaultValue: 2, |
||||
|
MinValue: 1, |
||||
|
MaxValue: 10, |
||||
|
Required: true, |
||||
|
DisplayName: "Max Concurrent Tasks", |
||||
|
Description: "Maximum number of vacuum tasks that can run simultaneously", |
||||
|
HelpText: "Limits the number of vacuum operations running at the same time to control system load", |
||||
|
Placeholder: "2 (default)", |
||||
|
Unit: config.UnitCount, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "min_volume_age_seconds", |
||||
|
JSONName: "min_volume_age_seconds", |
||||
|
Type: config.FieldTypeInterval, |
||||
|
DefaultValue: 24 * 60 * 60, // 24 hours
|
||||
|
MinValue: 1 * 60 * 60, // 1 hour
|
||||
|
MaxValue: 7 * 24 * 60 * 60, // 7 days
|
||||
|
Required: true, |
||||
|
DisplayName: "Minimum Volume Age", |
||||
|
Description: "Only vacuum volumes older than this duration", |
||||
|
HelpText: "Prevents vacuuming of recently created volumes that may still be actively written to", |
||||
|
Placeholder: "24", |
||||
|
Unit: config.UnitHours, |
||||
|
InputType: "interval", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "min_interval_seconds", |
||||
|
JSONName: "min_interval_seconds", |
||||
|
Type: config.FieldTypeInterval, |
||||
|
DefaultValue: 7 * 24 * 60 * 60, // 7 days
|
||||
|
MinValue: 1 * 24 * 60 * 60, // 1 day
|
||||
|
MaxValue: 30 * 24 * 60 * 60, // 30 days
|
||||
|
Required: true, |
||||
|
DisplayName: "Minimum Interval", |
||||
|
Description: "Minimum time between vacuum operations on the same volume", |
||||
|
HelpText: "Prevents excessive vacuuming of the same volume by enforcing a minimum wait time", |
||||
|
Placeholder: "7", |
||||
|
Unit: config.UnitDays, |
||||
|
InputType: "interval", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// GetBalanceTaskConfigSchema returns the schema for balance task configuration
|
||||
|
func GetBalanceTaskConfigSchema() *TaskConfigSchema { |
||||
|
return &TaskConfigSchema{ |
||||
|
TaskName: "balance", |
||||
|
DisplayName: "Volume Balance", |
||||
|
Description: "Redistributes volumes across volume servers to optimize storage utilization", |
||||
|
Icon: "fas fa-balance-scale text-secondary", |
||||
|
Schema: config.Schema{ |
||||
|
Fields: []*config.Field{ |
||||
|
{ |
||||
|
Name: "enabled", |
||||
|
JSONName: "enabled", |
||||
|
Type: config.FieldTypeBool, |
||||
|
DefaultValue: true, |
||||
|
Required: false, |
||||
|
DisplayName: "Enable Balance Tasks", |
||||
|
Description: "Whether balance tasks should be automatically created", |
||||
|
InputType: "checkbox", |
||||
|
CSSClasses: "form-check-input", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "imbalance_threshold", |
||||
|
JSONName: "imbalance_threshold", |
||||
|
Type: config.FieldTypeFloat, |
||||
|
DefaultValue: 0.1, // 10%
|
||||
|
MinValue: 0.01, |
||||
|
MaxValue: 0.5, |
||||
|
Required: true, |
||||
|
DisplayName: "Imbalance Threshold", |
||||
|
Description: "Trigger balance when storage imbalance exceeds this ratio", |
||||
|
Placeholder: "0.10 (10%)", |
||||
|
Unit: config.UnitNone, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "scan_interval_seconds", |
||||
|
JSONName: "scan_interval_seconds", |
||||
|
Type: config.FieldTypeInterval, |
||||
|
DefaultValue: 6 * 60 * 60, // 6 hours
|
||||
|
MinValue: 1 * 60 * 60, // 1 hour
|
||||
|
MaxValue: 24 * 60 * 60, // 24 hours
|
||||
|
Required: true, |
||||
|
DisplayName: "Scan Interval", |
||||
|
Description: "How often to scan for imbalanced volumes", |
||||
|
Unit: config.UnitHours, |
||||
|
InputType: "interval", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "max_concurrent", |
||||
|
JSONName: "max_concurrent", |
||||
|
Type: config.FieldTypeInt, |
||||
|
DefaultValue: 2, |
||||
|
MinValue: 1, |
||||
|
MaxValue: 5, |
||||
|
Required: true, |
||||
|
DisplayName: "Max Concurrent Tasks", |
||||
|
Description: "Maximum number of balance tasks that can run simultaneously", |
||||
|
Unit: config.UnitCount, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "min_server_count", |
||||
|
JSONName: "min_server_count", |
||||
|
Type: config.FieldTypeInt, |
||||
|
DefaultValue: 3, |
||||
|
MinValue: 2, |
||||
|
MaxValue: 20, |
||||
|
Required: true, |
||||
|
DisplayName: "Minimum Server Count", |
||||
|
Description: "Only balance when at least this many servers are available", |
||||
|
Unit: config.UnitCount, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// GetErasureCodingTaskConfigSchema returns the schema for erasure coding task configuration
|
||||
|
func GetErasureCodingTaskConfigSchema() *TaskConfigSchema { |
||||
|
return &TaskConfigSchema{ |
||||
|
TaskName: "erasure_coding", |
||||
|
DisplayName: "Erasure Coding", |
||||
|
Description: "Converts volumes to erasure coded format for improved data durability", |
||||
|
Icon: "fas fa-shield-alt text-info", |
||||
|
Schema: config.Schema{ |
||||
|
Fields: []*config.Field{ |
||||
|
{ |
||||
|
Name: "enabled", |
||||
|
JSONName: "enabled", |
||||
|
Type: config.FieldTypeBool, |
||||
|
DefaultValue: true, |
||||
|
Required: false, |
||||
|
DisplayName: "Enable Erasure Coding Tasks", |
||||
|
Description: "Whether erasure coding tasks should be automatically created", |
||||
|
InputType: "checkbox", |
||||
|
CSSClasses: "form-check-input", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "quiet_for_seconds", |
||||
|
JSONName: "quiet_for_seconds", |
||||
|
Type: config.FieldTypeInterval, |
||||
|
DefaultValue: 7 * 24 * 60 * 60, // 7 days
|
||||
|
MinValue: 1 * 24 * 60 * 60, // 1 day
|
||||
|
MaxValue: 30 * 24 * 60 * 60, // 30 days
|
||||
|
Required: true, |
||||
|
DisplayName: "Quiet For Duration", |
||||
|
Description: "Only apply erasure coding to volumes that have not been modified for this duration", |
||||
|
Unit: config.UnitDays, |
||||
|
InputType: "interval", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "scan_interval_seconds", |
||||
|
JSONName: "scan_interval_seconds", |
||||
|
Type: config.FieldTypeInterval, |
||||
|
DefaultValue: 12 * 60 * 60, // 12 hours
|
||||
|
MinValue: 2 * 60 * 60, // 2 hours
|
||||
|
MaxValue: 24 * 60 * 60, // 24 hours
|
||||
|
Required: true, |
||||
|
DisplayName: "Scan Interval", |
||||
|
Description: "How often to scan for volumes needing erasure coding", |
||||
|
Unit: config.UnitHours, |
||||
|
InputType: "interval", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "max_concurrent", |
||||
|
JSONName: "max_concurrent", |
||||
|
Type: config.FieldTypeInt, |
||||
|
DefaultValue: 1, |
||||
|
MinValue: 1, |
||||
|
MaxValue: 3, |
||||
|
Required: true, |
||||
|
DisplayName: "Max Concurrent Tasks", |
||||
|
Description: "Maximum number of erasure coding tasks that can run simultaneously", |
||||
|
Unit: config.UnitCount, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "fullness_ratio", |
||||
|
JSONName: "fullness_ratio", |
||||
|
Type: config.FieldTypeFloat, |
||||
|
DefaultValue: 0.9, // 90%
|
||||
|
MinValue: 0.5, |
||||
|
MaxValue: 1.0, |
||||
|
Required: true, |
||||
|
DisplayName: "Fullness Ratio", |
||||
|
Description: "Only apply erasure coding to volumes with fullness ratio above this threshold", |
||||
|
Placeholder: "0.90 (90%)", |
||||
|
Unit: config.UnitNone, |
||||
|
InputType: "number", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
{ |
||||
|
Name: "collection_filter", |
||||
|
JSONName: "collection_filter", |
||||
|
Type: config.FieldTypeString, |
||||
|
DefaultValue: "", |
||||
|
Required: false, |
||||
|
DisplayName: "Collection Filter", |
||||
|
Description: "Only apply erasure coding to volumes in these collections (comma-separated, leave empty for all)", |
||||
|
Placeholder: "collection1,collection2", |
||||
|
InputType: "text", |
||||
|
CSSClasses: "form-control", |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue