Files
GoMFT/components/config_form.templ
T
StarFleetCPTN b1db53b89e feat: Refactor Google Drive and Google Photos authentication handling
- Separate built-in authentication options for source and destination in configuration forms.
- Update UI components to reflect changes in authentication handling for Google Drive and Google Photos.
- Implement path validation for local directories in the configuration forms, enhancing user experience.
- Revise README to clarify supported source and destination types, including updated terminology for Google Drive.
- Introduce new API endpoint for path validation, improving error handling and user feedback.
2025-03-16 16:12:42 -07:00

445 lines
12 KiB
Templ

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"
"strconv"
)
type ConfigFormData struct {
Config *db.TransferConfig
IsNew bool
}
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 := 22
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, _ := strconv.Atoi(getCurrentYear())
sourceIncludeArchived := false
filePattern := ""
outputPattern := "${filename}"
destinationType := "local"
destinationPath := ""
destHost := ""
destPort := 22
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, _ := strconv.Atoi(getCurrentYear()) // Default to current year int
destIncludeArchived := false
archivePath := ""
archiveEnabled := false
deleteAfterTransfer := false
skipProcessedFiles := true
maxConcurrentTransfers := 4
rcloneFlags := ""
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
rcloneFlags = config.RcloneFlags
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',
// Path validation states
sourcePathValid: null,
sourcePathError: '',
destPathValid: null,
destPathError: '',
// 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;
});
}
}`,
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)
}
templ ConfigForm(ctx context.Context, data ConfigFormData) {
@LayoutWithContext(getConfigFormTitle(data.IsNew), ctx) {
<div class="flex flex-col items-center justify-center min-h-screen bg-secondary-50 dark:bg-secondary-900 py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-3xl w-full bg-white dark:bg-secondary-800 rounded-lg shadow-md p-8 border border-secondary-200 dark:border-secondary-700">
<div class="flex justify-center mb-6">
<div class="p-4 bg-primary-100 dark:bg-primary-900 rounded-full">
<i class="fas fa-cog text-primary-500 dark:text-primary-400 text-2xl"></i>
</div>
</div>
<h1 class="mt-2 text-center text-3xl font-extrabold text-secondary-900 dark:text-secondary-100">
{ getConfigFormTitle(data.IsNew) }
</h1>
<p class="mt-2 text-center text-sm text-secondary-600 dark:text-secondary-400">
Set up your file transfer configuration
</p>
<form
class="space-y-8"
if data.IsNew {
hx-post="/configs"
} else {
hx-post={ fmt.Sprintf("/configs/%d", data.Config.ID) }
}
hx-target="body"
hx-redirect="/configs"
hx-boost="true"
x-data={ getInitialData(data.Config) }
x-init="$nextTick(() => {
// Ensure initial form state displays correctly on load
sourceType = sourceType || 'local';
destinationType = destinationType || 'local';
sourcePort = sourcePort || 22;
destPort = destPort || 22;
})"
>
<!-- Name field -->
@common.NameField()
<!-- Source section -->
@common.SourceSelection()
<!-- Source type specific forms -->
<template x-if="sourceType === 'local'">
@source.LocalSourceForm()
</template>
<template x-if="sourceType === 'sftp'">
@source.SFTPSourceForm()
</template>
<template x-if="sourceType === 'ftp'">
@source.FTPSourceForm()
</template>
<template x-if="sourceType === 's3'">
@source.S3SourceForm()
</template>
<template x-if="sourceType === 'minio'">
@source.MinIOSourceForm()
</template>
<template x-if="sourceType === 'smb'">
@source.SMBSourceForm()
</template>
<template x-if="sourceType === 'webdav'">
@source.WebDAVSourceForm()
</template>
<template x-if="sourceType === 'nextcloud'">
@source.NextCloudSourceForm()
</template>
<template x-if="sourceType === 'gdrive'">
@source.GoogleDriveSourceForm()
</template>
<template x-if="sourceType === 'gphotos'">
@source.GooglePhotosSourceForm()
</template>
<!-- File pattern fields -->
@common.FilePatternFields()
<!-- Destination section -->
@common.DestinationSelection()
<!-- Destination type specific forms -->
<template x-if="destinationType === 'local'">
@destination.LocalDestinationForm()
</template>
<template x-if="destinationType === 'sftp'">
@destination.SFTPDestinationForm()
</template>
<template x-if="destinationType === 'ftp'">
@destination.FTPDestinationForm()
</template>
<template x-if="destinationType === 's3'">
@destination.S3DestinationForm()
</template>
<template x-if="destinationType === 'minio'">
@destination.MinIODestinationForm()
</template>
<template x-if="destinationType === 'smb'">
@destination.SMBDestinationForm()
</template>
<template x-if="destinationType === 'nextcloud'">
@destination.NextCloudDestinationForm()
</template>
<template x-if="destinationType === 'webdav'">
@destination.WebDAVDestinationForm()
</template>
<template x-if="destinationType === 'gdrive'">
@destination.GoogleDriveDestinationForm()
</template>
<template x-if="destinationType === 'gphotos'">
@destination.GooglePhotosDestinationForm()
</template>
<!-- Archive options -->
@common.ArchiveOptions()
<!-- Rclone flags -->
@common.RcloneFlags()
<!-- Form actions -->
<div class="flex justify-end mt-8">
<a href="/configs"
class="btn-secondary mr-4">
Cancel
</a>
<button
type="submit"
class="btn-primary">
<span class="flex items-center">
<i class="fas fa-save mr-2"></i>
if data.IsNew {
Create Configuration
} else {
Save Changes
}
</span>
</button>
</div>
</form>
</div>
<div class="mt-6 text-center text-xs text-secondary-500 dark:text-secondary-400">
<p>Configure your file transfer settings carefully for optimal performance</p>
</div>
</div>
}
}