package components import ( "fmt" "github.com/starfleetcptn/gomft/internal/db" "context" "github.com/starfleetcptn/gomft/components/providers/source" "github.com/starfleetcptn/gomft/components/providers/destination" "github.com/starfleetcptn/gomft/components/providers/common" ) type ConfigFormData struct { Config *db.TransferConfig IsNew bool // Fields for pre-rendering flags on edit InitialCommand *db.RcloneCommand SelectedFlagsMap map[uint]bool SelectedFlagValues map[uint]string } func getConfigFormTitle(isNew bool) string { if isNew { return "New Configuration" } return "Edit Configuration" } func getInitialData(config *db.TransferConfig) string { // Default values for a new configuration name := "" sourceType := "local" sourcePath := "" sourceHost := "" sourcePort := 0 // Initialize to 0 to trigger default setting sourceUser := "" sourcePassword := "" sourceKeyFile := "" sourceAuthType := "password" sourceBucket := "" sourceRegion := "" sourceAccessKey := "" sourceSecretKey := "" sourceEndpoint := "" sourceShare := "" sourceDomain := "" sourcePassiveMode := false sourceClientId := "" sourceClientSecret := "" sourceDriveId := "" sourceTeamDrive := "" // Google Photos source fields sourceReadOnly := false sourceStartYear := 2025 sourceIncludeArchived := false filePattern := "" outputPattern := "${filename}" destinationType := "local" destinationPath := "" destHost := "" destPort := 0 // Initialize to 0 to trigger default setting destUser := "" destPassword := "" destKeyFile := "" destAuthType := "password" destBucket := "" destRegion := "" destAccessKey := "" destSecretKey := "" destEndpoint := "" destShare := "" destDomain := "" destPassiveMode := false destClientId := "" destClientSecret := "" destDriveId := "" destTeamDrive := "" // Google Photos destination fields destReadOnly := false destStartYear := 2025 destIncludeArchived := false archivePath := "" archiveEnabled := false deleteAfterTransfer := false skipProcessedFiles := true maxConcurrentTransfers := 4 rcloneFlags := "" commandId := uint(1) // Default to 'copy' command commandFlags := "" useBuiltinAuthSource := true useBuiltinAuthDest := true // If editing an existing config, populate with those values if config != nil { name = config.Name sourceType = config.SourceType sourcePath = config.SourcePath sourceHost = config.SourceHost sourcePort = config.SourcePort sourceUser = config.SourceUser sourcePassword = config.SourcePassword sourceKeyFile = config.SourceKeyFile if sourceKeyFile != "" && sourcePassword == "" { sourceAuthType = "key" } sourceBucket = config.SourceBucket sourceRegion = config.SourceRegion sourceAccessKey = config.SourceAccessKey sourceSecretKey = config.SourceSecretKey sourceEndpoint = config.SourceEndpoint sourceShare = config.SourceShare sourceDomain = config.SourceDomain sourcePassiveMode = config.GetSourcePassiveMode() sourceClientId = config.SourceClientID sourceClientSecret = config.SourceClientSecret sourceDriveId = config.SourceDriveID sourceTeamDrive = config.SourceTeamDrive // Google Photos source fields if config.SourceReadOnly != nil { sourceReadOnly = *config.SourceReadOnly } // sourceStartYear = config.SourceStartYear if config.SourceIncludeArchived != nil { sourceIncludeArchived = *config.SourceIncludeArchived } filePattern = config.FilePattern outputPattern = config.OutputPattern destinationType = config.DestinationType destinationPath = config.DestinationPath destHost = config.DestHost destPort = config.DestPort destUser = config.DestUser destPassword = config.DestPassword destKeyFile = config.DestKeyFile if destKeyFile != "" && destPassword == "" { destAuthType = "key" } destBucket = config.DestBucket destRegion = config.DestRegion destAccessKey = config.DestAccessKey destSecretKey = config.DestSecretKey destEndpoint = config.DestEndpoint destShare = config.DestShare destDomain = config.DestDomain destPassiveMode = config.GetDestPassiveMode() destClientId = config.DestClientID destClientSecret = config.DestClientSecret destDriveId = config.DestDriveID destTeamDrive = config.DestTeamDrive // Google Photos destination fields if config.DestReadOnly != nil { destReadOnly = *config.DestReadOnly } // destStartYear = config.DestStartYear if config.DestIncludeArchived != nil { destIncludeArchived = *config.DestIncludeArchived } archivePath = config.ArchivePath archiveEnabled = config.GetArchiveEnabled() deleteAfterTransfer = config.GetDeleteAfterTransfer() skipProcessedFiles = config.GetSkipProcessedFiles() maxConcurrentTransfers = config.MaxConcurrentTransfers if maxConcurrentTransfers <= 0 { maxConcurrentTransfers = 1 // Ensure at least 1 concurrent transfer } rcloneFlags = config.RcloneFlags commandId = config.CommandID commandFlags = config.CommandFlags if config.UseBuiltinAuthSource != nil { useBuiltinAuthSource = *config.UseBuiltinAuthSource } else if sourceClientId != "" || sourceClientSecret != "" { useBuiltinAuthSource = false } if config.UseBuiltinAuthDest != nil { useBuiltinAuthDest = *config.UseBuiltinAuthDest } else if destClientId != "" || destClientSecret != "" { useBuiltinAuthDest = false } } // Return the JSON-formatted string with all the data, add new path validation states return fmt.Sprintf(`{ name: '%s', sourceType: '%s', sourcePath: '%s', sourceHost: '%s', sourcePort: %d, sourceUser: '%s', sourcePassword: '%s', sourceKeyFile: '%s', sourceAuthType: '%s', sourceBucket: '%s', sourceRegion: '%s', sourceAccessKey: '%s', sourceSecretKey: '%s', sourceEndpoint: '%s', sourceShare: '%s', sourceDomain: '%s', sourcePassiveMode: %v, sourceClientId: '%s', sourceClientSecret: '%s', sourceDriveId: '%s', sourceTeamDrive: '%s', sourceReadOnly: %v, sourceStartYear: %d, sourceIncludeArchived: %v, filePattern: '%s', outputPattern: '%s', destinationType: '%s', destinationPath: '%s', destHost: '%s', destPort: %d, destUser: '%s', destPassword: '%s', destKeyFile: '%s', destAuthType: '%s', destBucket: '%s', destRegion: '%s', destAccessKey: '%s', destSecretKey: '%s', destEndpoint: '%s', destShare: '%s', destDomain: '%s', destPassiveMode: %v, destClientId: '%s', destClientSecret: '%s', destDriveId: '%s', destTeamDrive: '%s', destReadOnly: %v, destStartYear: %d, destIncludeArchived: %v, useBuiltinAuthSource: %v, useBuiltinAuthDest: %v, archivePath: '%s', archiveEnabled: %v, deleteAfterTransfer: %v, skipProcessedFiles: %v, maxConcurrentTransfers: %d, rcloneFlags: '%s', commandId: %d, commandFlags: '%s', // Path validation states sourcePathValid: null, sourcePathError: '', destPathValid: null, destPathError: '', // Command configuration requiresDestination: true, // Methods for path validation checkPath(path, type) { if (!path) { this[type + 'PathValid'] = false; this[type + 'PathError'] = 'Path cannot be empty'; return; } fetch('/check-path?path=' + encodeURIComponent(path)) .then(response => response.json()) .then(data => { this[type + 'PathValid'] = data.valid; this[type + 'PathError'] = data.error || ''; }) .catch(error => { this[type + 'PathValid'] = false; this[type + 'PathError'] = 'Error checking path: ' + error.message; }); }, // Method to check if destination is required based on command type updateCommandRequirements() { // List of commands that don't require destination const listingCommands = ['ls', 'lsd', 'lsl', 'lsf', 'lsjson', 'listremotes']; const infoCommands = ['md5sum', 'sha1sum', 'size', 'version']; const dirCommands = ['mkdir', 'rmdir', 'rmdirs']; const destructiveCommands = ['delete', 'purge']; const specialSinglePathCommands = ['obscure']; // Get the command name from the command ID // This will need coordination with your backend to ensure the IDs match the commands let commandName = ''; switch(parseInt(this.commandId)) { // Correct mapping based on internal/db/migrations/009_add_rclone_tables.go case 1: commandName = 'copy'; break; case 2: commandName = 'sync'; break; case 3: commandName = 'bisync'; break; case 4: commandName = 'move'; break; case 5: commandName = 'delete'; break; case 6: commandName = 'purge'; break; case 7: commandName = 'mkdir'; break; case 8: commandName = 'rmdir'; break; case 9: commandName = 'rmdirs'; break; case 10: commandName = 'check'; break; case 11: commandName = 'ls'; break; case 12: commandName = 'lsd'; break; case 13: commandName = 'lsl'; break; case 14: commandName = 'lsf'; break; case 15: commandName = 'lsjson'; break; case 16: commandName = 'md5sum'; break; case 17: commandName = 'sha1sum'; break; case 18: commandName = 'size'; break; case 19: commandName = 'version'; break; case 20: commandName = 'cleanup'; break; case 21: commandName = 'dedupe'; break; case 22: commandName = 'copyto'; break; case 23: commandName = 'moveto'; break; case 24: commandName = 'listremotes'; break; case 25: commandName = 'obscure'; break; case 26: commandName = 'cryptcheck'; break; default: commandName = 'copy'; // Default to copy if ID is unknown } console.log('Command ID:', this.commandId, 'Command Name:', commandName); // Check if the command requires a destination if ( listingCommands.includes(commandName) || infoCommands.includes(commandName) || (dirCommands.includes(commandName) && !this.rcloneFlags.includes('--dst')) || destructiveCommands.includes(commandName) || specialSinglePathCommands.includes(commandName) || commandName === 'version' || commandName === 'listremotes' ) { this.requiresDestination = false; console.log('Destination not required for command:', commandName); } else { this.requiresDestination = true; console.log('Destination required for command:', commandName); } } }`, name, sourceType, sourcePath, sourceHost, sourcePort, sourceUser, sourcePassword, sourceKeyFile, sourceAuthType, sourceBucket, sourceRegion, sourceAccessKey, sourceSecretKey, sourceEndpoint, sourceShare, sourceDomain, sourcePassiveMode, sourceClientId, sourceClientSecret, sourceDriveId, sourceTeamDrive, sourceReadOnly, sourceStartYear, sourceIncludeArchived, filePattern, outputPattern, destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile, destAuthType, destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint, destShare, destDomain, destPassiveMode, destClientId, destClientSecret, destDriveId, destTeamDrive, destReadOnly, destStartYear, destIncludeArchived, useBuiltinAuthSource, useBuiltinAuthDest, archivePath, archiveEnabled, deleteAfterTransfer, skipProcessedFiles, maxConcurrentTransfers, rcloneFlags, commandId, commandFlags) } templ ConfigForm(ctx context.Context, data ConfigFormData) { @LayoutWithContext(getConfigFormTitle(data.IsNew), ctx) {

{ getConfigFormTitle(data.IsNew) }

Configure your file transfer settings using the form below

Configuration Details

Give your configuration a descriptive name and set up the source and destination locations.

Choose a descriptive name to identify this configuration.

Command Configuration

@common.RcloneFlags(data.Config.CommandID) // Pass current command ID
if !data.IsNew && data.InitialCommand != nil { // Pre-render flags if editing and command data is available @common.RcloneCommandFlagsContent(data.InitialCommand, data.SelectedFlagsMap, data.SelectedFlagValues) }

Source Configuration

@common.SourceSelection()

File Patterns

@common.FilePatternFields()

Destination Configuration

@common.DestinationSelection()

Advanced Options

Archive Settings

@common.ArchiveOptions()
Cancel
Configuration Tips

Configure your file transfer settings carefully for optimal performance. Use file patterns to filter which files are transferred, and consider using archive options to keep track of processed files.

} } templ formvalidation() { }