diff --git a/faas/.gitignore b/faas/.gitignore new file mode 100644 index 0000000..254defd --- /dev/null +++ b/faas/.gitignore @@ -0,0 +1 @@ +server diff --git a/faas/internal/domain/duration.go b/faas/internal/domain/duration.go index f08b8ef..69f37cd 100644 --- a/faas/internal/domain/duration.go +++ b/faas/internal/domain/duration.go @@ -33,7 +33,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error { } func (d Duration) Value() (driver.Value, error) { - return int64(d.Duration), nil + // Store as a PostgreSQL-compatible interval string + return d.Duration.String(), nil } func (d *Duration) Scan(value interface{}) error { @@ -44,54 +45,57 @@ func (d *Duration) Scan(value interface{}) error { switch v := value.(type) { case int64: - d.Duration = time.Duration(v) + // Handle legacy nanosecond values that were incorrectly stored + // If the value is extremely large (likely nanoseconds), convert it + if v > 1000000000000 { // More than 16 minutes in nanoseconds, likely a nanosecond value + d.Duration = time.Duration(v) + } else { + // Assume it's seconds for smaller values + d.Duration = time.Duration(v) * time.Second + } case string: duration, err := time.ParseDuration(v) if err != nil { - return err + return fmt.Errorf("cannot parse duration string: %s", v) } d.Duration = duration case []uint8: - // Handle PostgreSQL interval format (e.g., "8333333:20:00") + // Handle PostgreSQL interval format intervalStr := string(v) - // Try parsing as Go duration first + // Try parsing as Go duration first (for newer records) if duration, err := time.ParseDuration(intervalStr); err == nil { d.Duration = duration return nil } - // If that fails, try parsing PostgreSQL interval format - // Convert PostgreSQL interval "HH:MM:SS" to Go duration + // Handle PostgreSQL interval formats like "00:00:30" or "8333333:20:00" if strings.Contains(intervalStr, ":") { parts := strings.Split(intervalStr, ":") if len(parts) >= 2 { - // Parse hours, minutes, seconds format - var hours, minutes, seconds int64 + var hours, minutes, seconds float64 var err error - // Handle the case where we might have days as well (e.g., "8333333:20:00") - // or just hours:minutes:seconds switch len(parts) { case 2: // MM:SS - minutes, err = parseNumber(parts[0]) + minutes, err = strconv.ParseFloat(parts[0], 64) if err != nil { return fmt.Errorf("cannot parse minutes from interval: %s", intervalStr) } - seconds, err = parseNumber(parts[1]) + seconds, err = strconv.ParseFloat(parts[1], 64) if err != nil { return fmt.Errorf("cannot parse seconds from interval: %s", intervalStr) } case 3: // HH:MM:SS - hours, err = parseNumber(parts[0]) + hours, err = strconv.ParseFloat(parts[0], 64) if err != nil { return fmt.Errorf("cannot parse hours from interval: %s", intervalStr) } - minutes, err = parseNumber(parts[1]) + minutes, err = strconv.ParseFloat(parts[1], 64) if err != nil { return fmt.Errorf("cannot parse minutes from interval: %s", intervalStr) } - seconds, err = parseNumber(parts[2]) + seconds, err = strconv.ParseFloat(parts[2], 64) if err != nil { return fmt.Errorf("cannot parse seconds from interval: %s", intervalStr) } @@ -101,7 +105,26 @@ func (d *Duration) Scan(value interface{}) error { // Convert to duration totalSeconds := hours*3600 + minutes*60 + seconds - d.Duration = time.Duration(totalSeconds) * time.Second + d.Duration = time.Duration(totalSeconds * float64(time.Second)) + return nil + } + } + + // Handle PostgreSQL interval format like "30 seconds", "1 minute", etc. + if strings.Contains(intervalStr, " ") { + // Try to parse common PostgreSQL interval formats + intervalStr = strings.TrimSpace(intervalStr) + + // Replace PostgreSQL interval keywords with Go duration format + intervalStr = strings.ReplaceAll(intervalStr, " seconds", "s") + intervalStr = strings.ReplaceAll(intervalStr, " second", "s") + intervalStr = strings.ReplaceAll(intervalStr, " minutes", "m") + intervalStr = strings.ReplaceAll(intervalStr, " minute", "m") + intervalStr = strings.ReplaceAll(intervalStr, " hours", "h") + intervalStr = strings.ReplaceAll(intervalStr, " hour", "h") + + if duration, err := time.ParseDuration(intervalStr); err == nil { + d.Duration = duration return nil } } @@ -145,8 +168,3 @@ func (d Duration) Hours() float64 { return d.Duration.Hours() } -// Helper function to parse number from string, handling potential whitespace -func parseNumber(s string) (int64, error) { - s = strings.TrimSpace(s) - return strconv.ParseInt(s, 10, 64) -} diff --git a/faas/web/src/components/FunctionForm.tsx b/faas/web/src/components/FunctionForm.tsx index 2881c76..3efbe84 100644 --- a/faas/web/src/components/FunctionForm.tsx +++ b/faas/web/src/components/FunctionForm.tsx @@ -63,21 +63,61 @@ export const FunctionForm: React.FC = ({ } }, [opened]); + // Update form values when editFunction changes + useEffect(() => { + if (editFunction) { + form.setValues({ + name: editFunction.name || '', + app_id: editFunction.app_id || 'default', + runtime: editFunction.runtime || 'nodejs18' as RuntimeType, + image: editFunction.image || DEFAULT_IMAGES['nodejs18'] || '', + handler: editFunction.handler || 'index.handler', + code: editFunction.code || '', + environment: editFunction.environment ? JSON.stringify(editFunction.environment, null, 2) : '{}', + timeout: editFunction.timeout || '30s', + memory: editFunction.memory || 128, + owner: { + type: editFunction.owner?.type || 'team' as const, + name: editFunction.owner?.name || 'FaaS Team', + owner: editFunction.owner?.owner || 'admin@example.com', + }, + }); + } else { + // Reset to default values when not editing + form.setValues({ + name: '', + app_id: 'default', + runtime: 'nodejs18' as RuntimeType, + image: DEFAULT_IMAGES['nodejs18'] || '', + handler: 'index.handler', + code: '', + environment: '{}', + timeout: '30s', + memory: 128, + owner: { + type: 'team' as const, + name: 'FaaS Team', + owner: 'admin@example.com', + }, + }); + } + }, [editFunction, opened]); + const form = useForm({ initialValues: { - name: editFunction?.name || '', - app_id: editFunction?.app_id || 'default', - runtime: editFunction?.runtime || 'nodejs18' as RuntimeType, - image: editFunction?.image || DEFAULT_IMAGES['nodejs18'] || '', - handler: editFunction?.handler || 'index.handler', - code: editFunction?.code || '', - environment: editFunction?.environment ? JSON.stringify(editFunction.environment, null, 2) : '{}', - timeout: editFunction?.timeout || '30s', - memory: editFunction?.memory || 128, + name: '', + app_id: 'default', + runtime: 'nodejs18' as RuntimeType, + image: DEFAULT_IMAGES['nodejs18'] || '', + handler: 'index.handler', + code: '', + environment: '{}', + timeout: '30s', + memory: 128, owner: { - type: editFunction?.owner?.type || 'team' as const, - name: editFunction?.owner?.name || 'FaaS Team', - owner: editFunction?.owner?.owner || 'admin@example.com', + type: 'team' as const, + name: 'FaaS Team', + owner: 'admin@example.com', }, }, validate: {