diff --git a/README.md b/README.md index ea2fe60..0a65571 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging ## Features - **Multiple Storage Support**: Leverage rclone's extensive support for cloud storage providers: + - Google Drive + - Google Photos - Amazon S3 - MinIO - NextCloud @@ -150,6 +152,10 @@ services: - BACKUP_DIR=/app/backups - JWT_SECRET=change_this_to_a_secure_random_string - BASE_URL=http://localhost:8080 + # Google OAuth configuration (optional) + - GOOGLE_CLIENT_ID=your_google_client_id + - GOOGLE_CLIENT_SECRET=your_google_client_secret + # Email configuration - EMAIL_ENABLED=true - EMAIL_HOST=smtp.example.com - EMAIL_PORT=587 @@ -206,6 +212,10 @@ BACKUP_DIR=/app/backups JWT_SECRET=change_this_to_a_secure_random_string BASE_URL=http://localhost:8080 +# Google OAuth configuration (optional, for built-in authentication) +GOOGLE_CLIENT_ID=your_google_client_id +GOOGLE_CLIENT_SECRET=your_google_client_secret + # Email configuration EMAIL_ENABLED=true EMAIL_HOST=smtp.example.com @@ -226,6 +236,9 @@ EMAIL_PASSWORD=smtp_password - `BACKUP_DIR`: Directory for storing database backups - `JWT_SECRET`: Secret key for JWT token generation - `BASE_URL`: Base URL for generating links in emails (e.g., password reset links) +- Google OAuth configuration for built-in authentication: + - `GOOGLE_CLIENT_ID`: Your Google OAuth client ID + - `GOOGLE_CLIENT_SECRET`: Your Google OAuth client secret - Email configuration settings for system notifications and password resets: - `EMAIL_ENABLED`: Set to `true` to enable email functionality - `EMAIL_HOST`: SMTP server hostname @@ -332,6 +345,8 @@ User management features: ### Transfer Configuration Options 1. **Source/Destination Types**: + - Google Drive (with built-in or custom authentication) + - Google Photos (with built-in or custom authentication) - Local filesystem - Amazon S3 - MinIO (S3-compatible storage) @@ -343,32 +358,46 @@ User management features: 2. **Connection Options**: - Host/server addresses - Authentication (username/password or key files) + - OAuth2 authentication for Google services - Port configurations - Cloud credentials (access keys, secret keys) - Bucket and region settings - Custom endpoints - Custom rclone flags -3. **File Options**: +3. **Google Photos Specific Options**: + - Read-only mode for safer operations + - Start year filter for historical photos + - Include/exclude archived media + - Album path configuration + - Built-in or custom OAuth authentication + +4. **Google Drive Specific Options**: + - Folder ID for specific directory access + - Team/Shared Drive ID support + - Built-in or custom OAuth authentication + - Path-based navigation + +5. **File Options**: - File patterns for filtering (e.g., `*.txt`, `data_*.csv`) - Output patterns for dynamic naming - Archive options for transferred files - Skip already processed files to avoid duplicates - Concurrent file transfers (configurable per job) -4. **Performance Options**: +6. **Performance Options**: - **Multi-threaded File Transfers**: Process multiple files simultaneously for higher throughput - Configurable concurrency level (1-32 concurrent transfers) - Per-job concurrency settings to optimize for different storage types - Automatic transfer queue management to prevent overloading systems - Adaptive processing based on source/destination capabilities -5. **Schedule Options**: +7. **Schedule Options**: - Cron expressions for flexible scheduling - Manual execution - Enable/disable schedules -6. **Webhook Notifications**: +8. **Webhook Notifications**: - **Webhook Integration**: Send notifications to external systems when jobs complete - **Secure Authentication**: HMAC-SHA256 signature for webhook verification - **Custom Headers**: Add custom HTTP headers to webhook requests diff --git a/components/admin_tools.templ b/components/admin_tools.templ index 7a51949..dae3fee 100644 --- a/components/admin_tools.templ +++ b/components/admin_tools.templ @@ -85,7 +85,8 @@ script hideDialog(id string) { } script submitFormAndHideDialog(formId string, dialogId string) { - document.getElementById(formId).submit(); + // Use HTMX's API to trigger the request instead of bypassing it + htmx.trigger(document.getElementById(formId), 'submit'); document.getElementById(dialogId).classList.add("hidden"); } diff --git a/components/config_form.templ b/components/config_form.templ index ae4f575..6bb6ddc 100644 --- a/components/config_form.templ +++ b/components/config_form.templ @@ -7,6 +7,7 @@ import ( "github.com/starfleetcptn/gomft/components/providers/source" "github.com/starfleetcptn/gomft/components/providers/destination" "github.com/starfleetcptn/gomft/components/providers/common" + "strconv" ) type ConfigFormData struct { @@ -44,6 +45,10 @@ func getInitialData(config *db.TransferConfig) string { sourceClientSecret := "" sourceDriveId := "" sourceTeamDrive := "" + // Google Photos source fields + sourceReadOnly := false + sourceStartYear, _ := strconv.Atoi(getCurrentYear()) + sourceIncludeArchived := false filePattern := "" outputPattern := "${filename}" @@ -68,6 +73,10 @@ func getInitialData(config *db.TransferConfig) string { destClientSecret := "" destDriveId := "" destTeamDrive := "" + // Google Photos destination fields + destReadOnly := false + destStartYear, _ := strconv.Atoi(getCurrentYear()) // Default to current year int + destIncludeArchived := false archivePath := "" archiveEnabled := false @@ -75,6 +84,7 @@ func getInitialData(config *db.TransferConfig) string { skipProcessedFiles := true maxConcurrentTransfers := 4 rcloneFlags := "" + useBuiltinAuth := true // If editing an existing config, populate with those values if config != nil { @@ -96,12 +106,21 @@ func getInitialData(config *db.TransferConfig) string { sourceEndpoint = config.SourceEndpoint sourceShare = config.SourceShare sourceDomain = config.SourceDomain - sourcePassiveMode = config.SourcePassiveMode + 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 @@ -122,18 +141,32 @@ func getInitialData(config *db.TransferConfig) string { destEndpoint = config.DestEndpoint destShare = config.DestShare destDomain = config.DestDomain - destPassiveMode = config.DestPassiveMode + 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.ArchiveEnabled - deleteAfterTransfer = config.DeleteAfterTransfer + archiveEnabled = config.GetArchiveEnabled() + deleteAfterTransfer = config.GetDeleteAfterTransfer() skipProcessedFiles = config.GetSkipProcessedFiles() maxConcurrentTransfers = config.MaxConcurrentTransfers rcloneFlags = config.RcloneFlags + if config.UseBuiltinAuth != nil { + useBuiltinAuth = *config.UseBuiltinAuth + } else if destClientId != "" || destClientSecret != "" { + useBuiltinAuth = false + } } // Return the JSON-formatted string with all the data @@ -159,6 +192,9 @@ func getInitialData(config *db.TransferConfig) string { sourceClientSecret: '%s', sourceDriveId: '%s', sourceTeamDrive: '%s', + sourceReadOnly: %v, + sourceStartYear: %d, + sourceIncludeArchived: %v, filePattern: '%s', outputPattern: '%s', @@ -183,6 +219,11 @@ func getInitialData(config *db.TransferConfig) string { destClientSecret: '%s', destDriveId: '%s', destTeamDrive: '%s', + destReadOnly: %v, + destStartYear: %d, + destIncludeArchived: %v, + + useBuiltinAuth: %v, archivePath: '%s', archiveEnabled: %v, @@ -194,10 +235,13 @@ func getInitialData(config *db.TransferConfig) string { 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, + useBuiltinAuth, archivePath, archiveEnabled, deleteAfterTransfer, skipProcessedFiles, maxConcurrentTransfers, rcloneFlags) } @@ -277,6 +321,13 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { @source.NextCloudSourceForm() + + @source.GoogleDriveSourceForm() + + + + @source.GooglePhotosSourceForm() + @common.FilePatternFields() @@ -316,7 +367,14 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { @destination.WebDAVDestinationForm() + + + @destination.GoogleDriveDestinationForm() + + + @destination.GooglePhotosDestinationForm() + @common.ArchiveOptions() diff --git a/components/configs.templ b/components/configs.templ index f685849..49d4597 100644 --- a/components/configs.templ +++ b/components/configs.templ @@ -68,6 +68,9 @@ script triggerConfigDelete(dialogId string, configID uint, configName string) { type ConfigsData struct { Configs []db.TransferConfig + Error string + ErrorDetails string + Status string } templ Configs(ctx context.Context, data ConfigsData) { @@ -106,6 +109,27 @@ templ Configs(ctx context.Context, data ConfigsData) { console.log("Notyf initialized:", window.notyf); } + // Show status messages based on URL parameters + document.addEventListener('DOMContentLoaded', function() { + // Check for error message + const urlParams = new URLSearchParams(window.location.search); + const errorMsg = urlParams.get('error'); + const errorDetails = urlParams.get('details'); + const status = urlParams.get('status'); + + if (errorMsg) { + let message = errorMsg; + if (errorDetails) { + message += ": " + errorDetails; + } + window.notyf.error(message); + } + + if (status === 'gdrive_auth_success') { + window.notyf.success("Google Drive authentication completed successfully"); + } + }); + // Track all HTMX events for debugging document.addEventListener('htmx:beforeRequest', function(event) { @@ -244,8 +268,32 @@ templ Configs(ctx context.Context, data ConfigsData) {
{ config.Name }
+ + Google Drive configurations require authentication. Click the "Authenticate" button to complete setup. +
- - Disabled jobs will not run automatically. +
+ Jobs that are not enabled will not run automatically on schedule.
+ Simple one-click authentication using rclone's shared credentials +
+ Client ID from Google Cloud Console +
+ Client Secret from Google Cloud Console +
+ Folder ID to use as the root (leave empty for "My Drive") +
+ ID of the Shared Drive / Team Drive to use +
+ Path within the Drive where files will be uploaded +
After saving this configuration, you will need to authenticate with Google Drive.
The authentication process will require you to:
+ This is a one-time process for each configuration. The application will store your authorization token securely. +
You're using rclone's built-in authentication, which simplifies the setup process:
+ + Note: The built-in authentication uses shared credentials which have rate limits across all rclone users. + If you plan to transfer large amounts of data or run many concurrent transfers, consider creating your own credentials. +
To use Google Drive with your own credentials:
http://localhost:53682/
+ Only request read-only access to your photos +
+ Only include photos uploaded after this year +
+ Include archived photos and videos in media listings +
+ Path within Google Photos where files will be uploaded +
After saving this configuration, you will need to authenticate with Google Photos.
To use Google Photos with your own credentials:
All media items uploaded to Google Photos with rclone are stored in full resolution at original quality. These uploads will count towards storage in your Google Account.
+ Path within the Drive from which files will be transferred +
After saving this configuration, you'll need to authenticate with Google Drive on the configurations page.
+ Path within Google Photos to download files from +
When downloading from Google Photos, be aware that some original metadata may not be preserved. Google Photos processes and may compress some images upon upload.
%s