mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
- Introduced new source and destination forms for Backblaze B2 and Wasabi, allowing users to configure their cloud storage settings. - Updated the configuration generation logic to handle B2 and Wasabi, including necessary parameters such as access keys, bucket names, and endpoints. - Enhanced the user interface to provide clear instructions and security notes for both storage options, improving user experience and security awareness.
712 lines
26 KiB
Templ
712 lines
26 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"
|
|
)
|
|
|
|
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
|
|
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) {
|
|
<!-- Main Content -->
|
|
<section class="py-8 px-4">
|
|
<div class="mx-auto max-w-3xl">
|
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm p-6 md:p-8">
|
|
<!-- Form Header -->
|
|
<div class="mb-8 text-center">
|
|
<div class="flex justify-center mb-4">
|
|
<span class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-blue-50 dark:bg-blue-900">
|
|
<i class="fas fa-cog text-blue-600 dark:text-blue-300 text-2xl"></i>
|
|
</span>
|
|
</div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
{ getConfigFormTitle(data.IsNew) }
|
|
</h1>
|
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
|
Configure your file transfer settings using the form below
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Main Form -->
|
|
<form
|
|
class="space-y-6"
|
|
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';
|
|
|
|
// Set default ports based on connection type
|
|
if (sourcePort === 0 || !sourcePort) {
|
|
if (sourceType === 'sftp') {
|
|
sourcePort = 22;
|
|
} else if (sourceType === 'ftp') {
|
|
sourcePort = 21;
|
|
}
|
|
}
|
|
|
|
if (destPort === 0 || !destPort) {
|
|
if (destinationType === 'sftp') {
|
|
destPort = 22;
|
|
} else if (destinationType === 'ftp') {
|
|
destPort = 21;
|
|
}
|
|
}
|
|
|
|
// Initialize command requirements
|
|
updateCommandRequirements();
|
|
})"
|
|
x-effect="if (sourceType === 'sftp' && (sourcePort === 0 || sourcePort === 21)) {
|
|
sourcePort = 22;
|
|
console.log('Updating source port to 22 for SFTP');
|
|
} else if (sourceType === 'ftp' && (sourcePort === 0 || sourcePort === 22)) {
|
|
sourcePort = 21;
|
|
console.log('Updating source port to 21 for FTP');
|
|
}"
|
|
x-effect="if (destinationType === 'sftp' && (destPort === 0 || destPort === 21)) {
|
|
destPort = 22;
|
|
console.log('Updating destination port to 22 for SFTP');
|
|
} else if (destinationType === 'ftp' && (destPort === 0 || destPort === 22)) {
|
|
destPort = 21;
|
|
console.log('Updating destination port to 21 for FTP');
|
|
}"
|
|
>
|
|
|
|
<!-- Configuration Details Section -->
|
|
<div class="p-4 mb-4 bg-blue-50 border border-blue-100 rounded-lg dark:bg-blue-900/20 dark:border-blue-900">
|
|
<div class="flex items-center mb-2">
|
|
<i class="fas fa-info-circle text-blue-600 dark:text-blue-400 mr-2"></i>
|
|
<h3 class="text-lg font-medium text-blue-600 dark:text-blue-400">Configuration Details</h3>
|
|
</div>
|
|
<p class="text-sm text-blue-700 dark:text-blue-300">
|
|
Give your configuration a descriptive name and set up the source and destination locations.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Name field -->
|
|
<div class="mb-2">
|
|
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Configuration Name</label>
|
|
<input
|
|
type="text"
|
|
id="name"
|
|
name="name"
|
|
x-model="name"
|
|
required
|
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
placeholder="My Transfer Configuration"
|
|
/>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Choose a descriptive name to identify this configuration.</p>
|
|
</div>
|
|
|
|
<!-- Rclone Command Configuration Section -->
|
|
<div class="p-5 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
<h3 class="mb-4 text-xl font-bold text-gray-900 dark:text-white flex items-center">
|
|
<i class="fas fa-terminal mr-2 text-blue-500 dark:text-blue-400"></i>Command Configuration
|
|
</h3>
|
|
|
|
<!-- Additonal Rclone Flags -->
|
|
@common.RcloneFlags(data.Config.CommandID) // Pass current command ID
|
|
|
|
<!-- Container for flags, pre-rendered on edit, loaded via HTMX on new/change -->
|
|
<div id="command-flags-container" class="mt-4">
|
|
if !data.IsNew && data.InitialCommand != nil {
|
|
// Pre-render flags if editing and command data is available
|
|
@common.RcloneCommandFlagsContent(data.InitialCommand, data.SelectedFlagsMap, data.SelectedFlagValues)
|
|
}
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<!-- Source Configuration Section -->
|
|
<div class="p-5 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
<h3 class="mb-4 text-xl font-bold text-gray-900 dark:text-white flex items-center">
|
|
<i class="fas fa-upload mr-2 text-blue-500 dark:text-blue-400"></i>Source Configuration
|
|
</h3>
|
|
|
|
<!-- Source selection -->
|
|
@common.SourceSelection()
|
|
|
|
<div class="mt-4">
|
|
<button type="button"
|
|
class="text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800"
|
|
hx-post="/configs/test-connection"
|
|
hx-include="closest form"
|
|
hx-vals='{"providerType": "source"}'
|
|
hx-swap="none"
|
|
hx-indicator="#source-test-spinner">
|
|
<i class="fas fa-plug mr-1"></i> Test Source
|
|
<span id="source-test-spinner" class="htmx-indicator ml-2"><i class="fas fa-spinner fa-spin"></i></span>
|
|
</button>
|
|
<!-- Removed target div, result shown via toast -->
|
|
</div>
|
|
|
|
<!-- 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 === 'b2'">
|
|
@source.B2SourceForm()
|
|
</template>
|
|
|
|
<template x-if="sourceType === 'wasabi'">
|
|
@source.WasabiSourceForm()
|
|
</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>
|
|
</div>
|
|
|
|
<!-- File Pattern Section -->
|
|
<div class="p-5 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
<h3 class="mb-4 text-xl font-bold text-gray-900 dark:text-white flex items-center">
|
|
<i class="fas fa-filter mr-2 text-blue-500 dark:text-blue-400"></i>File Patterns
|
|
</h3>
|
|
|
|
<!-- File pattern fields -->
|
|
@common.FilePatternFields()
|
|
</div>
|
|
|
|
<!-- Destination Configuration Section (only shown if required) -->
|
|
<div x-show="requiresDestination" x-transition class="p-5 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
<h3 class="mb-4 text-xl font-bold text-gray-900 dark:text-white flex items-center">
|
|
<i class="fas fa-download mr-2 text-blue-500 dark:text-blue-400"></i>Destination Configuration
|
|
</h3>
|
|
|
|
<!-- Destination selection -->
|
|
@common.DestinationSelection()
|
|
|
|
<div class="mt-4">
|
|
<button type="button"
|
|
class="text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800"
|
|
hx-post="/configs/test-connection"
|
|
hx-include="closest form"
|
|
hx-vals='{"providerType": "destination"}'
|
|
hx-swap="none"
|
|
hx-indicator="#dest-test-spinner">
|
|
<i class="fas fa-plug mr-1"></i> Test Destination
|
|
<span id="dest-test-spinner" class="htmx-indicator ml-2"><i class="fas fa-spinner fa-spin"></i></span>
|
|
</button>
|
|
<!-- Removed target div, result shown via toast -->
|
|
</div>
|
|
|
|
<!-- 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 === 'b2'">
|
|
@destination.B2DestinationForm()
|
|
</template>
|
|
|
|
<template x-if="destinationType === 'wasabi'">
|
|
@destination.WasabiDestinationForm()
|
|
</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>
|
|
</div>
|
|
|
|
<!-- Advanced Options Section -->
|
|
<div class="p-5 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
<h3 class="mb-4 text-xl font-bold text-gray-900 dark:text-white flex items-center">
|
|
<i class="fas fa-cogs mr-2 text-blue-500 dark:text-blue-400"></i>Advanced Options
|
|
</h3>
|
|
|
|
<!-- Archive options -->
|
|
<div class="mb-6">
|
|
<h4 class="text-lg font-medium text-gray-900 dark:text-white mb-4">Archive Settings</h4>
|
|
@common.ArchiveOptions()
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<label for="destination_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
|
|
<span>Destination Path</span>
|
|
<span x-show="destinationType === 's3' || destinationType === 'b2' || destinationType === 'wasabi' || destinationType === 'minio'" class="text-xs text-gray-500">(Optional)</span>
|
|
</label>
|
|
<div class="relative">
|
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
|
<i class="fas fa-folder text-gray-400 dark:text-gray-500"></i>
|
|
</div>
|
|
<input type="text" id="destination_path" name="destination_path" x-model="destinationPath"
|
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
:placeholder="getDestinationPathPlaceholder()"
|
|
x-bind:required="requiresDestination && (destinationType !== 's3' && destinationType !== 'b2' && destinationType !== 'wasabi' && destinationType !== 'minio')" />
|
|
</div>
|
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
<span x-show="destinationType === 'local'">Directory where files will be saved</span>
|
|
<span x-show="destinationType === 'sftp' || destinationType === 'ftp'">Remote path on the server</span>
|
|
<span x-show="destinationType === 's3' || destinationType === 'b2' || destinationType === 'wasabi' || destinationType === 'minio'">Path within bucket. Leave empty for bucket root.</span>
|
|
<span x-show="destinationType === 'smb'">Share directory path</span>
|
|
<span x-show="destinationType === 'nextcloud' || destinationType === 'webdav'">Path on remote server</span>
|
|
<span x-show="destinationType === 'gdrive'">Folder in Google Drive</span>
|
|
<span x-show="destinationType === 'gphotos'">Album name in Google Photos</span>
|
|
</p>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="flex items-center justify-between pt-6 border-t border-gray-200 dark:border-gray-700">
|
|
<a href="/configs"
|
|
class="text-white bg-gray-500 hover:bg-gray-600 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800">
|
|
<i class="fas fa-arrow-left mr-2"></i>Cancel
|
|
</a>
|
|
<button
|
|
type="submit"
|
|
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
|
<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>
|
|
|
|
<!-- Help Card -->
|
|
<div class="mt-4 p-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-lightbulb text-yellow-400 text-xl"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h5 class="text-sm font-medium text-gray-900 dark:text-white">Configuration Tips</h5>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
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.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
}
|
|
} |